0

I was reading about LSP (Liskov Substitution Principle) in a book called Clean Architecture: A Craftsman's Guide to Software Structure and Design. I have a question regarding how this would be implemented in C++.

On page 95, there is a figure explaining LSP using a simple example:

enter image description here

My interpretation of the figure in C++:

// License.h
class License {
    public:
    virtual calcFee();
};


// PersonalLicense.h

#include "License.h"
class PersonalLicense : public License {
};


// BusinessLicense.h

#include "License.h"
class BusinessLicense : public License{
    private:
    someDataType users;
};

To my understanding, Billing will be using PersonalLicense and BusinessLicense (and other derivatives) potentially, so Billing.h will contain #include-s for all their header files:

// Billing.h
#include "PersonalLicense.h"
#include "BusinessLicense.h"
...

My concern is that
a) if there are a lot of License derivatives, Billing.h will have to include all of them, and
b) it looks like Billing.h will have to be edited to include every new kind of License.
Is this the case? Or am I missing something?

Update

After reading the answer by Ryathal, I am now under the impression that I misunderstood the above figure. I was understanding the diagram as Billing has a License, which doesn't seem to be correct.

The aforementioned answer says that "This would be used in something like a shopping cart that does have references to all the different license types". This makes me think that the original diagram is incorrect, and that it should look like this instead (ShoppingCart has a License).

enter image description here

Assumptios based on the above

  1. class License is defined in file named License.h.
  2. class PersonalLicense is defined in file named PersonalLicense.h and includes License.h.
  3. class BusinessLicense is defined in file named BusinessLicense.h and includes License.h.
  4. class ShoppingCart is defined in file named ShoppingCart.h and its member function has expressions as such:
...
list<License*> orders;
orders.add(new PersonalLicense());
orders.add(new BusinessLicense());
...

Questions

  1. Which of the diagrams is correct, if any?
  2. Is the diagram possible to implement using C++?
  3. If we have multiple License types, let's say 5 ~ 10, do we have to include each header into ShoppingCart.h? Or is there any way of including as little headers as possible? I'm unsure how all this works in practice.
11
  • 1
    "let's say that we have thousands of License types" <- even if you have thousands of license types in your system, you almost certainly shouldn't have thousands of license classes. This is an indication you are missing a layer (or more than one) of abstraction. Commented May 17, 2022 at 12:25
  • @PhilipKendall Thousands of License types was just an way of explaining my question. If there is future plan for more License types which are defined as classes, such as SchoolLicense, EducationLicense, DiscountedLicense, and FreeLicense, will it be just including all headers into the Billing.h?
    – Gooday2die
    Commented May 17, 2022 at 12:28
  • 3
    @Gooday2die - no, you shouldn't be including the headers of any of them in your Billing.h; that's the whole point. You'd only include Licence.h. The Billing class should not know anything about these, it should entirely work through the Licence interface. You'd have some other file (e.g. the one where the main() function is) where you'd create the Billing object by passing to it an instance of a concrete license subtype - you'd include the header file of that subtype there. Billing's constructor expects a License, but it will accept any subtype of License. Commented May 17, 2022 at 12:44
  • @FilipMilovanović -I understand that Billing shall not know or include any of following License types as well as that is the whole point of this diagram. However, I cannot think of any other ways of solving this problem and implementing this diagram with C++ while not including any of the PersonalLicense.h or BusinessLicense.h. Thus I was asking for "efficient way of solving this question" with this question.
    – Gooday2die
    Commented May 17, 2022 at 12:52
  • "However, I cannot think of any other ways of solving this problem and implementing this diagram with C++ while not including any of the PersonalLicense.h or BusinessLicense.h" - sorry, but why? You code will compile and run fine without including PersonalLicense.h or BusinessLicense.h in Billing.h or Billing.cpp, as long as Billing don't uses a type like PersonalLicense directly. Try it!
    – Doc Brown
    Commented May 17, 2022 at 14:33

3 Answers 3

2

First things first: the UML diagram

The original diagram is syntactically correct (except for the <I> on the box boundary, which should be «interface» centered above Licence in plain UML).

The diagram means, that Billing objects are associated to specific Licence objects and have access to their public feature, e.g. they may invoke their calculateFee() operation for calculating the bill. The open-headed arrow means moreover that it is guaranteed that Billing objects can efficiently find the associated License objects.

The revised diagram is also correct. Hint: the use of the white aggregation diamond is not wrong, but it does not add any meaning to the diagram (despite a popular belief): the UML specifications still leave its semantics undefined.

The main difference between the two diagrams is the multiplicity (it's unspecified in the first diagram, which in reality means that it's one to one if you take the UML specs by the book; it's one-to-many in the second).

C++ implementation - step 1, polymorphism

The diagram can be perfectly implemented in C++, using polymorphism. However, polymorphism requires some additional effort in C++.

The following example is not polymorphic and doomed to fail for your use case, because the way C++ handles objects:

class ShoppingCart {
public: 
    ...
private:
    std::vector<License> items;   // or std::list, if you prefer
};

The following alternative would be polymorphic:

class ShoppingCart {
public: 
    ...
private:
    std::vector<License*> items;   // the pointer allows to point to subtypes
};

But still, this is not sufficient. For polymorphism to work, the polymorphic object must also make the function that might be specialized virtual:

class License {
public: 
    virtual double calculateFee(); // ok double is not great for money, but it's another story
    virtual ~License();   // if one function is virtual, better foresee a virtual destructor as well
};

With this in place, the shopping cart would not need to know anything about the many different kind of licenses that exist. The compiler will make sure that in the following call the right member function is called:

total += items[n]->calculateFee(); 

C++ implementation, step 2: Headers

You should have self contained headers that include no more other headers than strictly needed to compile. So in your case ShoppingCart.h should only include License.h. If it doesn't compile, go to StackOverflow if necessary.

The only reason you would need the other headers, is if shopping cart would be responsible of the creation of the licenses:

orders.add(new PersonalLicense());
orders.add(new BusinessLicense()); 

But this should not be a responsibility of the shopping cart. Because this would indeed require the cart to know much to much on all the specializations. What you should have is some code like:

class ShoppingCart {
public: 
    void addItem(License* l); 
    double calculateTotal();  
private:
    std::vector<License*> items;   // the pointer allows to point to subtypes
};

So some other module would do the glue between the different kind of licenses and the shopping cart. If you'd really have to let the cart create the items, then you'd need to use a factory to keep it general.

And LSP in all this?

LSP means that your license specialization all continue to fulfill the contract guaranteed by License, so that whenever you use a License, you could exchange it with a more specialized license and the could would still work (as the contract is guaranteed).

If you don't have a polymorphic design in the first place, it makes no sense to talk about LSP.

10
  • I think the general outline of the answer is good and addresses most of the necessary points, given what the OP has communicated so far. Just wanted to comment on something, for clarity (since it was me who edited the question trying make it clear enough to be reopened): can't be 100% sure until we hear from the OP, but I'm under the impression that the OP's primary change to the diagram was not to turn the arrow into an aggregation or change the multiplicities (I think that was maybe coincidental, perhaps because of the UML tool used), ... 1/2 Commented May 19, 2022 at 3:06
  • ... , but to replace Billing with ShoppingCart due to OP misunderstanding Ryathal's answer (where ShoppingCart was introduced as a hypothetical third component that references/includes headers for both License derivatives and Billing). I think the OP is confused about how the arrows in the diagrams relate to #include-s. While the second diagram is syntactically correct and could also make sense in this hypothetical system, due to this misunderstanding, the OP might see the two as contradictory, so your remarks about the second one may not hit home. 2/2 Commented May 19, 2022 at 3:06
  • The answer covered most of my question that I was trying to ask. For Step 1, I am 100% clear with everything. For Step 2, So some other module would do the glue between the different kind of licenses and the shopping cart., I still have question left to ask for efficient implementation of the "other module." Let's assume that we have a class named ShoppingCartHelper which generates different kind of Licenses and hands it over to ShoppingCart (so that ShoppingCart does not have to know all diffrent kinds of licences). ... 1/2
    – Gooday2die
    Commented May 19, 2022 at 4:21
  • 1
    @Gooday2die A final remark is that if you have too many License specializations, you might overly on inheritance and should think somewhat about preferring composition over inheritance, to keep the type hierarchy as flat as possible ;-)
    – Christophe
    Commented May 19, 2022 at 17:58
  • 1
    Now everything is clear and makes sense right now. I will look into the factory method as well as avoiding too many licenses.
    – Gooday2die
    Commented May 20, 2022 at 3:45
2

For a demo of the Liskov Substitution Principle the header for Billing would only have License. The point of the LSP is that any object that extends another doesn't break that base functionality. Since Billing only knows about License for the LSP to be followed every extension must have an implementation of CalcFee that relies only on properties/functions that exist within license. When you violate the LSP its a good signal that your chosen abstraction is poor and you need to rethink the design. Inheritance really only works in somewhat small numbers (5 different licenses ok, 10+ might be a problem), in part because of the difficulty of following the LSP, which is why composition is more encouraged today as you can include similar functionality to objects, without directly relating them.

This is what Billing would look like:

Billing
{
  GetTotalFees(List<License> lic)
  {
    int total=0;
    foreach ( l in lic)
      total+=l.calcFee();
  }
}

This would be used in something like a shopping cart that does have references to all the different license types, so you could do something like:

Order.add(PersonalLicense);
Order.add(BusinessLicense);
Order.add(FishingLicense);
price=Billing.GetTotalFees(Order);
2
  • I guess I have confused a lot of people who tried to solve my problem with my language barrier. In the case like Billing that you have mentioned, Billing.h does not need to include any other headers but License.h. My original question that I was trying to ask was the part with something like a shopping cart that does have references to all the different license types. If we were to implement a "shopping cart", then will ShoppingCart.h has to include all different header files that declares Licenses? Is there any efficient and convenient way rather than adding each headers?
    – Gooday2die
    Commented May 17, 2022 at 15:00
  • 1
    @Gooday2die - I guess what's confusing to people is that what you are asking about (shopping cart having references to all the different license types) is not specific to inheritance/LSP or C++. You'd have to add some kind of reference to the declarations of all the things you're intending to use (whatever those things might be) in any language. Organizationally, in C++, these #include-s don't have to be separate files, it can all be a single header file (or something in between), if that's what you're asking. Commented May 17, 2022 at 16:22
0

If the user of the class tree - Billing in your example - needs to #include the header files for all your derived classes, then you're doing it wrong.

The first thing to try is not #include-ing them. If your code still compiles, then you didn't need them.

If it does break, then that suggests you're using features of the derived classes that aren't in the base one. This isn't actually a violation of the LSP, but is a "code smell". Ask yourself why the other classes don't implement that feature, even if it's a "do nothing" option. For example, if some licencees get discounts and others don't, then give all classes a "CalculateDiscount" function. Ones that don't get discounts can return zero. Such functions could be implemented in C++ as virtual functions on the base class, that derived classes may override if they wish.

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.