Java : preferred design when multiple methods shar

2019-08-29 08:01发布

问题:

Context: I believe that object creation and management in Java has a cost that we should bear in mind while programming. However, I don't know how big that cost is. Hence my question:

I have multiple functions that share the same arguments:

  • detectCollision(ArrayList<Mobile>, ArrayList<Inert>, double dt)
  • updatePositions(ArrayList<Mobile>, double dt)
  • etc.

As I see it, there are two ways to organize them (see code below):

  1. define (possibly static, but not necessarily) methods and forward the arguments for each call
  2. create a temporary object with private member variables and remove argument list.

Note that the Mover object has no private internal state and is just a bunch of algorithms that use the arguments ArrayList<Mobile>, ArrayList<Inert>, double dt.

Question: Which approach is the prefered one ? Does it have a cost ? Is there a more standard alternative ?

Here is a snippet illustrating the first point:

public class Mover{
    public static void updatePositions(ArrayList<Mobile>, double dt){...}
    /* remove the static keyword if you need polymorphism, it doesn't change the question */

    public static Collisions detectCollision(ArrayList<Mobile>, ArrayList<Inert>, double dt){...}
    //etc.
}

Here is a snippet illustrating the second point:

public class Mover{
    public Mover(ArrayList<Mobile>, ArrayList<Inert>, double dt){...}
    public void updatePositions(){...}
    public Collisions detectCollision(){...}
    //etc.

    private ArrayList<Mobile> mobiles;
    private ArrayList<Inert> inerts;
    //etc.
}

回答1:

I'd recommend you to go with the second variant. Besides the good readability it will also allow you to extend the class later (see SOLID -> open / closed principle). In general, I'd never create such utility classes, as it is not OOP (OOP Alternative to Utility Classes).



回答2:

While I think that static utility methods are not necessarily a Bad Thing™, you should be aware of the semantics of these methods. One of the most important points here is that static means that a method may not take part in any form of polymorphism. That is: It may not be overridden.

So regarding the design, the second option offers a greater degree of flexibility.


A side note: Even if you choose the second approach, you might eventually dispatch to a (non-public) static method internally:

class Mover {
    private final List<? extends Mobile> mobiles;

    public void updatePositions(){
        MyStaticUtilityMethods.updatePositions(this.mobiles);
    }
}

Note that I'm not generally recommending this, but pointing it out as one option that may be reasonable in many cases.


It might be off-topic, but there is another degree of freedom for the design here. You could (and at least should consider to) go one step further: As far as one can guess (!) from the method names, you might consider having interfaces PositionUpdater and a CollisionDetector. Then you could store instances of classes implementing these interfaces inside your Mover class, and dispatch the actual call to these. This way, you can easily combine the different aspects of what comprises a "Mover".

(I know, this does not answer the actual question. In fact, it just "defers" it to whether the PositionUpdater should receive the data as arguments, or receive them at construction time...)

You could then assign instances of different implementations of the PositionUpdater interface to a Mover instance. For example, you could have concrete classes called LinearMovementPositionUpdater and RandomWalkPositionUpdater. Passing instances of these classes (which are both implementing the PositionUpdater interface) to the Mover allows you to change one aspect of the implementation of a Mover - basically, without touching any code! (You could even change this at runtime!).

This way, the responsibilities for

  • updating the positions
  • detecting the collisions

are clearly encapsulated, in view of several of the SOLID principles that already have been mentioned in another answer.

But again: This is just a hint. Judging whether this approach is actually sensible and applicable in one particular case is what software engineers get all the $$$ for.



回答3:

The problem you're having here is mostly due to having an orchestrated model (and which probably has an anaemic domain model to accompany it).

If instead you use an event driven approach these methods would simply disappear and be replaced by event handlers each of which would respond to the appropriate events independently of the other, taking whatever information they need from the initial event.

This means that you wouldn't have to deal with passing parameters or working out which parameters to pass -- each 'handler (method)' would know what it needs and would take it, rather than having to have an external orchestrator understand what data is needed and pass it in.

The orchestrator model breaks encapsulation by having it 'know' about the information needs of the components it is orchestrating.

Now, this isn't a java specific problem. It applies to most modern object-oriented languages. It doesn't matter if you're doing java, c-sharp, or c++. It's a general pattern.

To break away from this thinking, read about DDD (Domain Driven Design) and Event Sourcing.