0

I was reading about composition and I'm wondering if it violates the O in SOLID, and what should be done to fix the situation. In the article the author uses a Fruit and an Apple. At the end the writer admits it would be better as a "is-a" relationship rather than "has-a".

Suppose however, you decided to stick with composition and you constructed your Fruit class like this:

class Fruit {
    public void peel(){
      //code to peel fruit.
    }
}

and Apple:

public void peel() {
    fruit.peel();
}

If I updated my Fruit class, for example, a method called eat, I would then have to also update my Apple class to accomdate the new eat method.

Questions:

  1. Does this violate the O is SOLID? To me, it does because every time the Fruit changes, you have to update Apple to implement it.
  2. Would providing a getter for the Fruit and then calling the required method be better? If you were determined to make composition work, how would you handle updates?

Example:

Apple:

  public Fruit getFruit(){
       return this.fruit;
  }

Client:

Fruit fruit = apple.getFruit();
fruit.eat();

Ideally you would refactor this to a is-a relationship and regardless of what type of fruit it is, the sub class can override eat().

  • 7
    You can violate any of the SOLID principles with composition or inheritance. In fact, you can violate any of the SOLID principles with a simple standalone class too. Both composition and inheritance are tools; they're not any kind of indicator of code quality, maintainability, or adherence to any particular design guidelines. If your code violates SOLID principles, the reason will usually not be as simple as a choice between inheritance vs composition, and the solution will generally not be that simple either. – Ben Cottrell Oct 29 '17 at 19:47
  • SOLID is not a set of general purpose principles, so there is nothing to "violate". Each guideline in SOLID is specific to a purpose. – Frank Hileman Oct 30 '17 at 22:14
6

The O/C principle talks about the fragility of the module or interface -- it goes to the tension between wanting to introduce new capabilities, and making a change that would break existing clients and consumers forcing them to also made a corresponding code change or suffer being broken, such as perhaps not compiling or not running.

O/C is tells us to consider protecting existing code bases so that — without modification — they work as they were originally designed even in the face of other elements of the system being upgraded.

To me, it does because every time the Fruit changes, you have to update Apple to implement it.

By contrast, your scenario is about feature upgrade: you are specifically wanting the clients and consumers making use of the new features offered as new methods (so they have to change by definition).

The admonishment of the O/C principle doesn't apply, as this situation is not about protecting an existing code base from breaking without comparable modification, but rather about what it takes to offer or expose new functionality to the ultimate consumers or clients.

In your scenario, getting/using new functionality (and propagating this thru the code base) is the concern rather than then the concern of breaking existing code to work as is when one part of the system changes by offering more functionality.

  • If I understand correctly, I'm not violating the O in SOLID if I have to update a class because the interface or backing class changed, correct? – user287100 Oct 29 '17 at 23:49
  • It is a violation of Open/Closed if you have to update another class because it is now BROKEN due to a change some where else. If it still works as is (even though doesn't use the new functionality) it is not a violation. We expect classes that want to access new methods to have to change their implementations, even if that means changes have to be propagated thru various intermediate classes. – Erik Eidt Oct 30 '17 at 4:42
  • Trading off composition for inheritance may make it easier to propagate new functionality to the ultimate client/consumer but having to change to propagate new functionality (e.g. use new methods) doesn't violate O/C since O/C is about unchanged clients/consumers continuing to work with old existing functionality/methods. – Erik Eidt Oct 30 '17 at 4:47
1

If I updated my Fruit class, for example, a method called eat, I would then have to also update my Apple class to accomdate the new eat method.

Why would you have to update Apply to accomodate the new eat method? Apple's type is not bound to Fruit, so the only time you must change Apple is when peel's signature changes.

Does this violate the O is SOLID? To me, it does because every time the Fruit changes, you have to update Apple to implement it.

Again, you would only need to update Apple if it inherited from Fruit. Since there is no inheritance, you don't have to update Apple every time Fruit changes.

Would providing a getter for the Fruit and then calling the required method be better? If you were determined to make composition work, how would you handle updates?

Definitely not. The purpose behind composition is to hide the collaborators. Clients using Apple shouldn't know or care that Apple has an instance of Fruit.

At the end the writer admits it would be better as a "is-a" relationship rather than "has-a".

I think this may explain some of your confusion. Apple and Fruit is a poor example because in the real world an apple is a fruit. An apple doesn't use a fruit to peel itself. Let me propose a different example. Let's have Apple and ApplePeeler where ApplePealer looks like this:

interface ApplePeeler {
    void peel(Apple apple);
}

Now it makes (somewhat) more sense for Apple to have an ApplePeeler and use it to peel itself

class Apple {
    private ApplePeeler peeler;

    public peel() {
        peeler.peel(this);
    }
}

If I add a new method to ApplePeeler, I don't have to change Apple, but if I change the signature of ApplePeeler.peel it may break Apple.

Going a step further it doesn't really make sense for an apple to peel itself, so I would not define peel on Apple. The composition would occur where an Apple needs to be peeled.

class Apple {
}

class AppleSauceMaker {
    private ApplePeeler peeler;
    private ApplePress press;

    Sauce makeSauce(Apple apple) {
        return press.press(peeler.peel(apple));
    }
}
  • I would have to update Apple because the Fruit object is hidden behind apple. If I wanted to eat it, I would have to implement a method inside apple which calls fruit.eat(); – user287100 Oct 29 '17 at 20:05
  • Yes, but there is no obligation for you to allow Apple to be eaten. If Apple inherited from Fruit, then Apple must be able to be eaten. That's why I say the example isn't a good example because any operation that you can think of for Fruit naturally apply to Apple too. An apple is a fruit. Think about the ApplePeeler above. If I add a repair method to my ApplePeeler it doesn't mean I have to change Apple at all. – Samuel Oct 29 '17 at 20:09
  • My whole problem is, as you pointed out Apple is a Fruit, so it would naturally inherit Fruit and it's methods, but if you're using composition to model what would be a is-a relationship, then you'd have to update Apple if you wanted to eat it. Does that violate the O in SOLID? Updating a class, because the backing class changed – user287100 Oct 29 '17 at 20:12
  • 1
    The problem is the example given on the page. It's better in a is-a context rather than a has-a. – user287100 Oct 29 '17 at 20:15
  • In this example the fruit abstraction is actually entirely unneeded. You would only need a Fruit class at all if you had a FruitSauceMaker, FruitPeeler, FruitPress, and so on. Even if you also had an TomatoSauceMaker and also had the same Tomato peeler and press, you STILL wouldn’t need a Fruit abstraction— you’d treat Apple and Tomato as complete separate things with no shared parent classes at all....... you only need the extra abstraction when you actually have the FruitSauceMaker that really does need to operate on multiple kinds of fruits. – RibaldEddie Oct 29 '17 at 22:46
-1

If I add a new method to ApplePeeler, I don't have to change Apple,

You do have to change Apple to accommodate the new method. When you implement the interface, you must implement all its methods. But I agree with you in the broader sense.

:)

  • 1
    Apple doesn't implement ApplePeeler – Samuel Oct 29 '17 at 22:52
  • 2
    I agree with this answer, but it might be better as a comment. – user287100 Oct 29 '17 at 23:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy