0

I came across an article about LSP https://stackify.com/solid-design-liskov-substitution-principle/ . It seems that a strategy pattern could be implemented here but I cannot see how to implement it.

My idea is to let the brewCoffee be the strategy method, in which i state what kind of coffee should be brewed. The following is an overview over the design,enter image description here

Here's my solution so far: enter image description here

BasicCoffeeMachine should only be able to brew filter coffee, while PremiumCoffeeMachine should be able to brew both filter and espresso. But as of yet, both machines can do both. Should i introduce another interface, one being for BasicCoffeeMachine and one for PremiumCoffeeMachine?

Any feedback overall?

How would you implement the strategy pattern in this case?

7
  • please don't cross-post: stackoverflow.com/questions/73627635/… "Cross-posting is frowned upon as it leads to fragmented answers splattered all over the network..."
    – gnat
    Commented Sep 6, 2022 at 21:01
  • 1
    okay i deleted it. I would highly appreaciate if you could atleast give some feedback. Thanks in advance
    – Gullit
    Commented Sep 6, 2022 at 21:07
  • The CoffeeMachine hierarchy (prior to your introduction of BrewEspresso and BrewFilterCoffee) is already the Strategy pattern. CoffeeMachine plays the role of the abstract strategy interface, while Premium and Basic represent concrete strategies. A client would get injected with one or the other, and it would call the brewCoffee virtual method at some point. Now, with your addition, if you want BasicCoffeeMachine to only be able to brew filter coffee, change its constructor to accept a BrewFilterCoffee parameter (rather than IStrategy). Commented Sep 6, 2022 at 22:30
  • So if i understand it correct, the design is complete and follows the strategy pattern - and I only need to change the constructor of BasicCoffeeMachine to only accept brewFilterCoffee? Sorry my english is not good. Thanks!
    – Gullit
    Commented Sep 6, 2022 at 22:37
  • What I'm saying is that the original design in the article was already created in a way that allows it to be used like the Strategy pattern by the calling code. You then added your IStrategy (and its derivatives) on top of that, so now you essentially have two instances of the Strategy pattern, one using the other (which may be an overkill in this case, but generally speaking is something that can be done (it's not "wrong") - depends on what you're trying to do).1/2 Commented Sep 7, 2022 at 11:13

2 Answers 2

2

In your second diagram, the PremiumCoffeeMachine and the BasicCoffeMachine both inherit the composite aggregation of their generalization CoffeeMachine with IStrategy.

The way you modelled the diagram is without any specific constraint. So any IStrategy could be used with any CoffeeMachine. If this is too flexible, you have several choices:

  • Introduce a distinction between different families of IStrategy and associate them only with the appropriate family of coffee machines.
  • Redefine/Subset the association of specific coffee machine with specific strategies.
  • Simply express the constraint in regarding the associated types. This is more flexible in the model.

Whatever your choice, the fact that a subclass contraints the usable strategies, i.e. strengthen the pre-conditions, the design will not be LSP compliant.

Other unrelated remarks: there is no need to repeat the composite aggregation of CoffeeMachine for all its specializations: the specialized machines inherit the associations from their ancestors (by the way, the arrow head of inheritance should be hollow, i.e a large white triangle).

2
  • Oh, I didn't notice at first that CoffeeMachine references IStrategy (I left a comment under the question earlier). Looks like the OP is doing this for practice, but depending on the requirements in an real-world scenario, another option (as an alternative to your last paragraph) would be to actually remove that, and (if possible) let the derivatives decide if they are going to use IStrategy or not. BTW, I think the (second) diagram is auto-generated from code by an IDE, so I'm not sure that it follows UML conventions 100% (it's probably more of a visualization of what references what). Commented Sep 7, 2022 at 11:38
  • 1
    @FilipMilovanović Thanks for these insights. Indeed, letting the derivates decide if IStrategy is to be used or prefer some specific subtype of IStrategy, is what I meant in the first bullet
    – Christophe
    Commented Sep 7, 2022 at 19:37
0

You can have a property "canMakeExpresso" which each class implements. And the method "makeExpresso" throws an error / exception if you call it for a class that doesn't implement it.

You decide if you want a property "canMakeRegularCoffee" which returns true for all existing classes - and a method "makeRegularCoffee" which is always implemented.

Now these two properties are properties of the coffee machine that you don't expect to change while the coffee machine is plugged in. You could also check if the coffee machine has water, has the right ground coffee or coffee beans etc. which you would assume to change at runtime. I'd keep those separate. So "canMakeExpresso" would return true even if the machine has no water.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.