code reuse in Methods.class vs strategy pattern an

2020-05-19 03:44发布

问题:

Status: Answers of Fendy and Glen Best are equally acceptable and honored by me but since one can be accepted and bounty be given, I choose Fendy's answer.

Scenario:

If I have some code that has to be reused many times in many classes (rarely with minor parameter changes which is obvious) and concurrent threads, Which approach to go for?

The code that has to be reused can be any sane thing (with appropriate care of static and non-static context in mind and method making techniques). It can be an algorithm, A DB method doing connect,operate,close. Anything.

  1. Make some class like MyMethods.class and put all those methods in it.

    1.a. Make methods static and call (by all classes and concurrent threads) directly as MyMethods.someMethod();

    1.b. Make methods non-static and at the time to call them, instantiate the whole class by MyMethods mm = MyMethods(); mm.someMethod();

  2. Use Strategy pattern stated at https://en.wikipedia.org/wiki/Strategy_pattern (code attached here with).

  3. Use dependency injection stated at https://en.wikipedia.org/wiki/Dependency_injection#Java

Problems:

  1. Some people would say Unit test http://en.wikipedia.org/wiki/Unit_testing wont be possible with this approach, will make trouble in swaping out latter. if you want to test your class and use a mock version of the dependency

    1.a. Will there be any problems with concurrent calls or multiple classes? specially in JDBC static methods for just an example?

    1.b. I think it would make too much memory load as a whole class would be instanticated many times just to call one or two methods

  2. Thats way over my head, do explain that and or any advantages/disadvantages

  3. I do Not want to use a framework in context to this question.. Thats way over my head, do explain that and or any advantages/disadvantages

  4. Awaiting any other strategies or recommendations, if any.

Request: Please only answer if you are experienced and know the implications deeply and can comprehensively, with your answer, help me and the community as a whole!

Code:

/** The classes that implement a concrete strategy should implement this.
* The Context class uses this to call the concrete strategy. */
interface Strategy {
    int execute(int a, int b); 
}

/** Implements the algorithm using the strategy interface */
class Add implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Add's execute()");
        return a + b;  // Do an addition with a and b
    }
}

class Subtract implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Subtract's execute()");
        return a - b;  // Do a subtraction with a and b
    }
}

class Multiply implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Multiply's execute()");
        return a * b;   // Do a multiplication with a and b
    }    
}

// Configured with a ConcreteStrategy object and maintains
// a reference to a Strategy object 
class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int a, int b) {
        return this.strategy.execute(a, b);
    }
}

/** Tests the pattern */
class StrategyExample {
    public static void main(String[] args) {
        Context context;

        // Three contexts following different strategies
        context = new Context(new Add());
        int resultA = context.executeStrategy(3,4);

        context = new Context(new Subtract());
        int resultB = context.executeStrategy(3,4);

        context = new Context(new Multiply());
        int resultC = context.executeStrategy(3,4);

        System.out.println("Result A : " + resultA );
        System.out.println("Result B : " + resultB );
        System.out.println("Result C : " + resultC );
    }
}

回答1:

Your question actually has two meanings.

that has to be reused many times in many classes

It can be a context of design pattern (reusable component) or memory cost (class instantiation). Talking from two different perspective:

Memory Cost (I had little experience on this, but let me share my experience)

This section actually only cover 2 kind of instantiation.

First is static (or DI instantiation in composition root)

  • Eager instantiation, means all class will be instantiated when application start
  • One time instantiation only

Non-static

  • Lazy instantiation, means class will only be instantiated when needed
  • One time instantiation every use

In short, static will cost high if the class is many, and non-static will cost high if the request is high (inside for loop, for example). But it should not make your application heavy. Most operation in java / csharp are creates objects though.

Class Reusability

1 - mega monolithic code (one god class able to do almost everything)

Advantages:

  1. Easy to search for code (still depends though), you know that every logic lies there so you just need to look at that big class
  2. If it is static, you can just call it anywhere without worrying about the instantiation

Disadvantages:

  1. Any modification for one method creates risk for error in other places
  2. Violates SRP, means this class can be changed by various reason, not only one
  3. Especially in versioning, it is harder to merge if modification happen in separated branches, resulting effort in synchronize code

1a / static class / singleton pattern

Advantages:

  1. Easy to use
  2. Can be used anywhere (just reference and call, and done)
  3. Not need to instantiate object

Disadvantages:

  1. Hard to unit test (it is hard to mock, and at latter time, you will find it is taking time to prepare the test environment. Especially with data
  2. If stateful (has state), it is hard to determine current state, during debugging. Moreover, it is hard to determine which function changes the state, in can be changed from everywhere
  3. Tend to have much parameters (maybe around 5-11)

Some point about static class: see this question

2 strategy pattern

Actually this has the same design with 3 or composition over inheritance.

3 dependency injection

Advantages:

  • Easy to mock and unit test
  • Must be stateless. It is easier to debug and unit test if the class is stateless
  • Support to be refactored

The disadvantage:

  • Hard to debug for those who does not familiar with interfaces (everytime you redirect to the method, it goes to interface's)
  • Creates layering which will resulting to mapping

State / Stateless

I think states plays important rules in your application design. Usually developers try to avoid having states in business logic code, such as:

// get data
if(request.IsDraft){
  // step 1
  // step 2
}
else{
  // step 1
  // step 3
}

Developers tend to put the logic in other stateless class, or at least methods such as:

// get data
if(request.IsDraft){
    draftRequestHandler.Modify(request);
}
else{
    publishedRequestHandler.Modify(request);
}

It will provide in better readability, and easier for modification and unit tests. There is one design pattern state pattern or hierarchial state machine pattern too, especially to handle some cases like this.

Single Responsibility Principle

IMHO, this principle is has the most benefit if followed. The advantages are:

  1. In versioning, the changes are clear about which class has been modified and why
  2. In DI, several smaller classes can be wired up, creating flexibility in both usage and unit test
  3. Increase modularity, low coupling and high cohesion

TDD (Test Driven Development)

This design does not guarantee your code free of bugs. In cons of costing time at design phase and layering effort, it has the benefit of:

  1. Easy to mock object for unit test
  2. Easier to refactor because of unit test and modularity
  3. Easier to maintain / extend

Some useful sources

Service Locator Anti Pattern

Using Decorator for cross-cutting concern

My 2 cents:

The benefit of using interface (also apply for composition ofer inheritance)

Doing top down design / DI design

Final Thoughts

Those designs and strategies are not the key which will determine your application structure. It still the architect who will determine it. I prefer to follow some principles such as SOLID, KISS and GRASP, rather than deciding what is the best structure. It is said that Dependency Injection follow the most of those principles, but too much abstraction and incorrect components design will resulting the same with the misuse of singleton pattern.



回答2:

1.a. Make methods static and call (by all classes and concurrent threads) directly as MyMethods.someMethod();

1.b. Make methods non-static and at the time to call them, instantiate the whole class by MyMethods mm = MyMethods(); mm.someMethod();

The choice between these two depends on the functionality of MyMethods.class. If MyMethods is supposed to be stateless then it's a good approach to go with static methods. Otherwise, if one method call depends on another and MyMethods have states (i.e. non-final fields) then use the second option.

Use Strategy pattern stated at https://en.wikipedia.org/wiki/Strategy_pattern (code attached here with).

Use this pattern if MyMethods are to be extended with different classes for different purposes and if you select which code to run depending on your context. As the wiki says, if you the algorithm to be used is not known before the runtime (depends on some conditions) this is the way to go. According to your specification of MyMethods you do not have such problems.

Use dependency injection stated at https://en.wikipedia.org/wiki/Dependency_injection#Java

Same answer as above. The thing with dependency injection is in inversion of control. A class that uses MyMethods does not know about actual the implementation of MyMethods, but the injection of real implementation is delegated to some higher-level authority. It abstracts external dependencies from the context where it is going to be used. Again, if MyMethods are to be stateless and constant (not planned to change, and the behavior of methods within the class does not depend on the context where they are used) you do not need these patterns as it would just mean over engineering.

I would conclude that you should use Strategy or DI pattern if logic of MyMethods depends on the context from which they are run. If this is constant (e.g. Java's Math class does not care who or in what context someone calls sqrt(), max() or pow()) then static methods are the way to go.


Regarding problems:

Problems you stated are not present when you use MyMethods with static methods. You will have to test whether your methods return correct values for particular arguments and that's it. I don't believe that there would be much more trouble testing actual implementation of Strategy in Strategy pattern or implementation of interface that are injected through Dependency injection. What might be harder is testing the classes that use strategy because sometimes it's not easy to recreate the context in which particular strategy will be executed as it often depends on user input, but it's definitely not impossible. Dependency injection is, as far as I'm concerned, great for testing because you can separate unit under test from dependency which you can easily mock.



回答3:

  1. The main question: code reuse

    If I have some code that has to be reused many times in many classes (rarely with minor parameter changes which is obvious) and concurrent threads, Which approach to go for?

    Because you're not considering any cut-&-paste, I think you mean:

    ... reused many times by many classes ...

    What you're asking is nothing special or specific. It's common that code is reused, either within a single app or across multiple apps. The common answer: use object-oriented design/programming. Put the code in a class, create an object as an instance, call the object...

    1a. Reuse via static methods:

    Make methods static and call (by all classes and concurrent threads) directly as MyMethods.someMethod()

    • If your class is stateless (no instance variables), this is a great approach.
    • If your class has class-level state (static instance variables only), but the variables are read-only (immutable), this is a good approach.
    • If your class has class-level state (static instance variables only) and the variables change values (mutable), then this can be an appropriate approach. However, if you want your class to be accessible from multiple threads, you must make it thread-safe: make your methods synchronized, or have internal code that synchronizes (mutually-exclusive thread access) for all data reads and writes.
    • If your code has object-level state (non-static instance variables), this approach won't work - impossible to access non-static instance variables without instantiating an object.

    1b. Reuse via non-static methods, with object instantiation:

    Make methods non-static and at the time to call them, instantiate the whole class by MyMethods mm = MyMethods(); mm.someMethod();

    • If your class has static instance variables only, this is a poor approach, because instantiating the object achieves nothing
    • If your class has non-static instance variables - this is the only approach. Mandatory to instantiate an object to access the variables.
    • If the instantiated objects are to be used across multiple threads, they should be (in order of preference):
      • stateless (no instance variables) - really an option for 1a - no need to instantiate
      • immutable (read-only non-static instance variables)
      • synchronized on all data reads & writes
  2. Use Strategy pattern

    Strategy pattern can be good practice. But it has little to do with your overall question. Strategy pattern is used for a specific reason - to swap the implementation of algorithm/processing logic "on-the-fly" without impacting the caller.

  3. Use dependency injection

    Dependency injection is used for these reasons:

    • Factory & object cache functionality: removes from your code responsibility for object creation, caching and lookup
    • Intermediation for object sharing: allows various classes to share the same object instance (stored in a given scope/context), without the two classes directly passing the object between themselves
    • "Wiring control" between object instances - setting up object associations, and under CDI, support for interceptor, decorator and observer patterns

    This can be very good practice, if used appropriately. In your case, this could only ever apply under option 1b. Dependency injection is all about object instantiation and provision into variables.

Problems:

  1. Some people would say Unit test wont be possible

    • Mocking frameworks (and hand-coded unit testing) deal with replacing classes with mock logic, all the time. It's a very normal scenario. You can extend a class to mock it's logic - if it doesn't have final public methods. Also, you can transfer method declarations to an interface, have the class implement the interface, and then mock by implmenting the interface with a different class.
    • In other words, this is not a constraint/force affecting any of your options

    1a. See above

    1b. Memory Load

    I think it would make too much memory load as a whole class would be instanticated many times just to call one or two methods

    A small issue. Depending on the data within each object instance (the instance variables), each object instance could be as small as a dozen bytes or as large as megabytes - but usually slanted towards the lower end (often < 1kB). The memory consumption of class code itself is not replicated each time the class is instantiated.

    Of course, it's good practice to minimise the volume of objects, according to your requirements - don't create a new instance if you already have a useable one. Create fewer object instances and share them across your app - passing them into constructor methods and setter methods. Dependency injection is a good way to share object instances "automatically" without passing them into constructors/setters.

  2. See above

  3. See above