How to solve the violations of the Law of Demeter?

后端 未结 10 1245
一个人的身影
一个人的身影 2021-01-29 22:23

A colleague and I designed a system for our customer, and in our opinion we created a nice clean design. But I\'m having problems with some coupling we\'ve introduced. I could t

相关标签:
10条回答
  • My understanding of consequences of the Law of Demeter seems to be different to DrJokepu's - whenever I've applied it to object oriented code it's resulted in tighter encapsulation and cohesion, rather than the addition of extra getters to contract paths in procedural code.

    Wikipedia has the rule as

    More formally, the Law of Demeter for functions requires that a method M of an object O may only invoke the methods of the following kinds of objects:

    1. O itself
    2. M's parameters
    3. any objects created/instantiated within M
    4. O's direct component objects

    If you have a method which takes 'kitchen' as a parameter, Demeter says you cannot inspect the components of the kitchen, not that you can only inspect the immediate components.

    Writing a bunch of functions just to satisfy the Law of Demeter like this

    Kitchen.GetCeilingColour()
    

    just looks like a total waste of time for me and actually gets is my way to get things done

    If a method outside of Kitchen is passed a kitchen, by strict Demeter it can't call any methods on the result of GetCeilingColour() on it either.

    But either way, the point is to remove the dependency on structure rather than moving the representation of the structure from a sequence of chained methods to the name of the method. Making methods such as MoveTheLeftHindLegForward() in a Dog class doesn't do anything towards fulfilling Demeter. Instead, call dog.walk() and let the dog handle its own legs.

    For example, what if the requirements change and I will need the ceiling height too?

    I'd refactor the code so that you are working with room and ceilings:

    interface RoomVisitor {
      void visitFloor (Floor floor) ...
      void visitCeiling (Ceiling ceiling) ...
      void visitWall (Wall wall ...
    }
    
    interface Room { accept (RoomVisitor visitor) ; }
    
    Kitchen.accept(RoomVisitor visitor) {
       visitor.visitCeiling(this.ceiling);
       ...
    }
    

    Or you can go further and eliminate getters totally by passing the parameters of the ceiling to the visitCeiling method, but that often introduces a brittle coupling.

    Applying it to the medical example, I'd expect a SolubleAdminstrationRoute to be able to validate the medicine, or at least call the medicine's validateForSolubleAdministration method if there's information encapsulated in the medicine's class which is required for the validation.

    However, Demeter applies to OO systems - where data is encapsulated within the objects which operate upon the data - rather than the system you're talking about, which has different layers an data being passed between the layers in dumb, navigatable structures. I don't see that Demeter can necessarily be applied to such systems as easily as to monolithic or message based ones. (In a message based system, you can't navigate to anything which isn't in the grams of the message, so you're stuck with Demeter whether you like it or not)

    0 讨论(0)
  • 2021-01-29 23:00

    For the BLL my idea was to add a property on Medicine like this:

    public Boolean IsSoluble
    {
        get { return AdministrationRoute.Soluble; } 
    }
    

    Which is what I think is described in the articles about Law of Demeter. But how much will this clutter the class?

    0 讨论(0)
  • 2021-01-29 23:00

    Instead of going all the way and providing getters/setters for every member of every contained object, one simpler alteration you can make that offers you some flexibility for future changes is to give objects methods that return their contained objects instead.

    E.g. in C++:

    class Medicine {
    public:
        AdministrationRoute()& getAdministrationRoute() const { return _adminRoute; }
    
    private:
        AdministrationRoute _adminRoute;
    };
    

    Then

    if (Medicine.AdministrationRoute.Soluble) ...
    

    becomes

    if (Medicine.getAdministrationRoute().Soluble) ...
    

    This gives you the flexibility to change getAdministrationRoute() in future to e.g. fetch the AdministrationRoute from a DB table on demand.

    0 讨论(0)
  • 2021-01-29 23:05

    The third problem is very simple: Discipline.ToString() should evaluate the Name property That way you only call Kind.Discipline

    0 讨论(0)
  • 2021-01-29 23:07

    I know I'm going to get downvoted to total annihilation but I must say I sort of dislike Law of Demeter. Certainly, things like

    dictionary["somekey"].headers[1].references[2]
    

    are really ugly, but consider this:

    Kitchen.Ceiling.Coulour
    

    I have nothing against this. Writing a bunch of functions just to satisfy the Law of Demeter like this

    Kitchen.GetCeilingColour()
    

    just looks like a total waste of time for me and actually gets is my way to get things done. For example, what if the requirements change and I will need the ceiling height too? With the Law of Demeter, I will have to write an other function in Kitchen so I can get the Ceiling height directly, and in the end I will have a bunch of tiny getter functions everywhere which is something I would consider quite some mess.

    EDIT: Let me rephrase my point:

    Is this level of abstracting things so important that I shall spend time on writing 3-4-5 levels of getters/setters? Does it really make maintenance easier? Does the end user gain anything? Is it worth my time? I don't think so.

    0 讨论(0)
  • 2021-01-29 23:07

    Concerning the first example with the "soluble" property, I have a few remarks:

    1. What is "AdministrationRoute" and why would a developer expect to get a medicine's soluble property from it? The two concepts seem entirely unrelated. This means the code does not communicate very well and perhaps the decomposition of classes you already have could be improved. Changing the decomposition could lead you to see other solutions for your problems.
    2. Soluble is not a direct member of medicine for a reason. If you find you have to access it directly then perhaps it should be a direct member. If there is an additional abstraction needed, then return that additional abstraction from the medicine (either directly or by proxy or façade). Anything that needs the soluble property can work on the abstraction, and you could use the same abstraction for multiple additional types, such as substrates or vitamins.
    0 讨论(0)
提交回复
热议问题