Jas
Jas

Reputation: 15093

Refactoring to partial classes and cyclic dependencies

I have a huge class A which I want to refactor and extract a partial class with some of its behavior. Now I do not want to refactor A in one step into 10 partial classes but i want first to take first behavior, refactor it into B, then in a continuous refactor manner next time i touch this class take another behavior refactor it into C.

then A which serves as a kind of a main will accept DI way B and C and use them as helpers.

My problem is this:

As this is a partial refactor (continous...) B and C are dependent on some logic in A its impossible to refactor A into B and C without having B and C dependant on A logic otherwise i would need to break immediately A into something like 10 or 20 classes.

what i'm left with is this:

  1. B and C accept as well (ugly) A in DI way although ugly this will allow me to do continuos refactors event worse A is not ready yet was not constructed until i call its constructor with the helpers B and C.
  2. Do the complete refactor - can't do it! its too much too complex and too risky i prefer to take it step by step - so its also not acceptable! i need small steps in the refactors and build the refactors step by step. Every time i touch any code i do a little cleanup and refactors (legacy code), cannot refactor the whole thing at once.
  3. DI with setter also not acceptable, its a mess I prefer dependencies in ctor.

Any ideas on this? any pattern for this?

Upvotes: 1

Views: 585

Answers (4)

Fendy
Fendy

Reputation: 4643

Usually these are the steps / rules I used to do refactoring. I usually do not find any problem when following these steps. The examples are in C#.

  1. Removing static functions (optional)

    If you want to remove static function, usually I wrap it as a class and doing default field injection (in C# is property). Please note usually this has been done for stateless functions.

    public static class StaticExample{
        public static void DoSomething(int a){ /*code here*/ }
    }
    public class WrapperExample{
        public void DoSomething(int a){ return StaticExample.DoSomething(a); }
    }
    public class Consumer{
        public WrapperExample wrapperExample = new WrapperExample();
        public void ConsumeBefore(int a){ StaticExample.DoSomething(a); }
        public void ConsumeAfter(int a){ wrapperExample.DoSomething(a); }
    }
    

    This resulting to zero impact for current logic and providing nice baseline for more refactoring (dependency injection, modify Wrapper to not use static, etc.

  2. Refactor the end point first

    Usually a function executes several steps. Some examples is:

    • cart checkout: get cart items, validate cart, validate payment, subtract storage quantity, mark cart as complete, [print invoice] <-- this is the endpoint

    • answer stackoverflow question: get answer, validate answer, insert / mark answer as published, [send notifications] <-- this is the endpoint

    This is usually easier to be done because:

    1. usually there is no external resource (parameter / variable / data) required during the execution of endpoint,
    2. usually it does not require much data manipulation,

    3. usually it has interaction into external dependency such as database or printer making it is worth to extract.

    The point 1 usually solve your cyclic dependency with refactored class.

  3. Make it stateless. If it stateful, refactor to parameters

    Say that one of your class has this:

    public class Foo(
        public string Bar = "";
        public void DoSomething(){
            // the code you want to refactor using Bar
        }
    )
    

    Become

    public class Foo(
        public string Bar = "";
        public RefactoredClass refactoredClass = new RefactoredClass();
        public void DoSomething(){
            refactoredClass.DoSomething(Bar);
        }
    )
    
  4. Several similiar parameters become a DTO

    This refactor are purposed to simplify the too many parameter. Usually this implies if the set of parameter have some relations each other. For example (maybe not a real life scenario):

    string cartId, string productId, int quantity --> CartItem
    string cartId, string productId, int quantity, string userName, string invoiceNumber --> this is not right
    

    The first example can be refactored into DTO since the parameters are still in the same context (CartItem). In the second example, the context for userName and invoiceNumber is not related to productId and quantity.

    It can lead to violations of SRP since looks like the function will process 2 different thing. However, if the userName and invoiceNumber be embedded into a CartHeader, which has array of CartItems, it become one context, maybe to print invoice.

This is my 2 cents. I don't have any sources or references.

Upvotes: 2

Jas
Jas

Reputation: 15093

found an "official" answer in an official refactoring extract class pattern which talks about this exact problem:

http://sourcemaking.com/refactoring/extract-class

Mechanics

06 Decide how to split the responsibilities of the class. Create a new class to express the split-off responsibilities. If the responsibilities of the old class no longer match its name, rename the old class. Make a link from the old to the new class. You may need a two-way link. But don’t make the back link until you find you need it.

so sometimes there is no way around but making this two way link.

Upvotes: 1

Lesstat
Lesstat

Reputation: 238

As you haven't really stated where those dependencies you speak of come from I'm doing some guessing.

You said after refactoring step by step the classes B and C would have cyclic dependencies back to A. It sounds like the logic in A is written in one big messy method doing a lot of different things or all the methods of A are very dependent on the state of A which isn't exactly great (especially if you want to unit test). That said I would recommend trying to start structuring the logic of A in methods that have one responsibility or are (ideally) not state dependent (e.g maybe you should pass the state in via arguments).

As a result it should be easier to extract methods into helper classes without creating cyclic dependencies.

Upvotes: 1

3biga
3biga

Reputation: 392

Try Mediator pattern. All interactions between A and partial classes should be encapsulated in mediator class.

Upvotes: -1

Related Questions