I find this question very interesting after reading the part of "Effective C++" about public inheritance. Before it would be common sense for me to say yes, because every square is a rectangle, but not necessarily other way around. However consider this code:
void makeBigger(Rectangle& r) {
r.setWidth(r.width() + 10);
}
This code is perfectly fine for a Rectangle, but would break the Square object if we passed it to makeBigger - its sides would become unequal.
So how can I deal with this? The book didn't provide an answer (yet?), but I'm thinking of a couple of ways of fixing this:
Override setWidth() and setHeight() methods in Square class to also adjust the other side.
Drawback: code duplication, unnecessary 2 members of Square.
For Square not to inherit from Rectangle and be on its own - have size, setSize() etc.
Drawback: weird - squares are rectangles after all - it would be nice to reuse Rectangle's features such as right angles etc.
Make Rectangle abstract (by giving it a pure virtual destructor and defining it) and have a third class that represents rectangles that are not squares and inherits from Rectangle. That will force us to change the above function's signature to this:
void makeBigger(NotSquare& r);
Can't see any drawbacks except having an extra class.
Is there a better way? I'm leaning towards the third option.
This is one of the key principles in OO design that I find gets handled incorrectly. Mr Meyer does an excellent job of of discussing it the book you are referring to.
The trick is to remember that the principles must be applied to concrete use cases. When using inheritence, remember that the key is that the "is a" relationship applies to an object when you want to use that object as a ... So whether a square is a rectangle or not depends on what you are going to be doing with rectangles in the future.
If you will be setting width and height of a rectangle independently, then no, a square is not a rectangle (in the context of your software) although it is mathematically. Thus you have to consider what you will be doing with your base objects.
In the concrete example you mention, there is a canonical answer. If you make makeBigger a virtual member function of rectangle, then each one can be scaled in a way that is appropriate to a class. But this is only good OO design if all the (public) methods which apply to a rectangle will apply to a square.
So let's see how this applies to your efforts so far:
I see this kind of thing in production code pretty often. It's excusable as a kludge to fix a gap in an otherwise good design, but it is not desirable. But it's a problem because it leads to code which is syntactically correct, but semantically incorrect. It will compile, and do something, but the meaning is incorrect. Lets say you are iterating over a vector of rectangles, and you scale the width by 2, and the height by 3. This is semantically meaningless for a square. Thus it violates the precept "prefer compile time errors to runtime errors".
Here you are thinking of using inheritance in order to re-use code. There's a saying "use inheritance to be re-used, not to re-use". What this means is, you want to use inheritance to make sure the oo code can be re-used elsewhere, as its base object, without any manual rtti. Remember that there other mechanisms for code re-use: in C++ these include functional programming and composition.
If square's and rectangles have shared code (e.g. computing the area based on the fact that they have right angles), you can do this by composition (each contains a common class). In this trivial example you are probably better off with a function though, for example:
compute_area_for_rectangle(Shape* s){return s.GetHeight() * s.GetWidth());}
provided at a namespace level.
So if both Square and Rectangle inherit from a base class Shape, Shape having the following public methods: draw(), scale(), getArea() ..., all of these would be semantically meaningful for whatever shape, and common formulas could be shared via namespace level functions.
I think if you meditate on this point a little, you'll find a number of flaws with your third suggestion.
Regarding the oo design perspective: as icbytes mentioned, if you're going to have a third class, it makes more sense that this class be a common base that meaningfully expresses the common uses. Shape is ok. If the main purpose is to draw the objects than Drawable might be another good idea.
There are a couple other flaws in the way you expressed the idea, which may indicate a misunderstanding on your part of virtual destructors, and what it means to be abstract. Whenever you make a method of a class virtual so that another class may override it, you should declare the destructor virtual as well (S.M. does discuss this in Effective C++, so I guess you would find this out on your own). This does not make it abstract. It becomes abstract when you declare at least one of the methods purely virtual -- i.e. having no implementation
virtual void foo() = 0; // for example
This means that the class in question cannot be instantiated. Obviously since it has at least one virtual method, it should also have the destructor declared virtual.
I hope that helps. Keep in mind that inheritence is only one method by which code can be re-used. Good design comes out of the optimal combination of all methods.
For further reading I highly recommend Sutter and Alexandrescu's "C++ Coding Standards", especially the section on Class Design and Inheritence. Items 34 "Prefer composition to inheritence" and 37 "Public inheritence is substitutability. Inherit, not to reuse, but to be reused.
It turns out the easier solution is
Rectangle makeBigger(Rectangle r)
{
r.setWidth(r.width() + 10);
return r;
}
Works perfectly well on squares, and correctly returns a rectangle even in that case.
[edit]
The comments point out that the real problem is the underlying call to setWidth. That can be fixed in the same way:
Rectangle Rectangle::setWidth(int newWidth) const
{
Rectangle r(*this);
r.m_width = newWidth;
return r;
}
Again, changing the width of a square gives you a rectangle. And as the const shows, it gives you a new Rectangle without changing the existing Rectangle The previous function now becomes even easier:
Rectangle makeBigger(Rectangle const& r)
{
return r.setWidth(r.width() + 10);
}
Except from having an extra class there are no serious drawbacks of your 3rd solution (called also Factor out modifiers). The only I can think of are:
Suppose I have a derived Rectangle class with one edge being a half of the other, called for example HalfSquare. Then according to your 3rd solution I'd have to define one more class, called NotHalfSaquare.
If you have to introduce on more class then let it be rather Shape class both Rectangle, Square and HalfSquare derive from
If you want your Square to be a Rectangle, it should publicly inherit from it. However, this implies that any public methods that work with a Rectangle must be appropriately specialised for a Square. In this context
void makeBigger(Rectangle& r)
should not be a standalone function but a virtual member of Rectangle which in Square is overridden (by providing its own) or hidden (by using makeBigger in the private section).
Regarding the issue that some things you can to do a Rectangle cannot be done to a Square. This is a general design dilemma and C++ is not about design. If somebody has a reference (or pointer) to a Rectangle that actually is a Square and want to do an operation that makes no sense for a Square, then you must deal with that. There are several options:
1 use public inheritance and make Square throw an exception if an operation is attempted that is not possible for a Square
struct Rectangle {
double width,height;
virtual void re_scale(double factor)
{ width*=factor; height*=factor; }
virtual void change_width(double new_width) // makes no sense for a square
{ width=new_width; }
virtual void change_height(double new_height) // makes no sense for a square
{ height=new_height; }
};
struct Square : Rectangle {
double side;
void re_scale(double factor)
{ side *= factor; } // fine
void change_width(double)
{ throw std::logic_error("cannot change width for Sqaure"); }
virtual void change_height(double)
{ throw std::logic_error("cannot change height for Sqaure"); }
};
This really is awkward and not appropriate if change_width() or change_height() are integral parts of the interface. In such a case, consider the following.
2 you can have one class Rectangle (which may happen to be square) and, optionally, a separate class Square that can be converted (static_cast<Rectangle>(square)) to a Rectangle and hence act as a Rectangle, but not be modified like a Rectangle
struct Rectangle {
double width,height;
bool is_square() const
{ return width==height; }
Rectangle(double w, double h) : width(w), height(h) {}
};
// if you still want a separate class, you can have it but it's not a Rectangle
// though it can be made convertible to one
struct Square {
double size;
Square(Rectangle r) : size(r.width) // you may not want this throwing constructor
{ assert(r.is_square()); }
operator Rectangle() const // conversion to Rectangle
{ return Rectangle(size,size); }
};
This option is the correct choice if you allow for changes to the Rectangle that can turn it into a Square. In other words, if your Square is not a Rectangle, as implemented in your code (with independently modifiable width and height). However, since Square can be statically cast to a Rectangle, any function taking an Rectangle argument can also be called with a Square.
You say: "because every square is a rectangle" and here the problem lies exactly. Paraphrase of famous Bob Martin's quote:
The relationships between objects are not shared by their
representatives.
(original explanation here: http://blog.bignerdranch.com/1674-what-is-the-liskov-substitution-principle/)
So surely every square is a rectangle, but this doesn't mean that a class/object representing a square "is a" class/object representing a rectangle.
The most common real-world, less abstract and intuitive example is: if two lawyers struggle in the court representing a husband and a wife in the context of a divorce, then despite the lawyers are representing the people during a divorce and being currently married they are not married themselves and are not during a divorce.
My idea:
You have a superclass, called Shape. Square inherits from Shape. It has the method resize(int size ). A Rectangle is ClassRectangle, inheriting from Shape but implementing interface IRecangle. IRectangle has method resize_rect(int sizex, int size y ).
In C++ interfaces are created by the usage of so called pure virtual methods. It is not fully well implemented like in c# but for me this is even better solution than third option. Any opinions ?
Related
I'm creating a collision system for a game and having a range of bounding volume classes (AABB, OBB, 18DOP etc) and they're a child of "GameObjectElement" (abstract class). The idea being each game object can hold a vector of GameObjectElements.
The problem is if I create an AABB and attach it to a GameObject is now becomes a GameObjectElement. However, to check for a collision, I need it to be an AABB not a GameObjectElement.
My first instinct was to use casting but during university, my lecturer despised this. I've looked at polymorphism and upcasting and downcasting but can't quite piece together a solution (though I think I have all the pieces to the puzzle).
If anyone can suggest a way to fix this problem, it'd be much appreciated.
Thank you
If you want to store various members of a class hierarchy, you usually use pointers. (Other possibilities are variants and 'chameleon classes' of same size). This is because vectors need to store elements of the same type and size. What you need here is a pure virtual function for collision checking (e.g. virtual bool collision(const int x, const int y) = 0) in the base class, an implementation of that virtual function in each descendant (e.g., virtual bool collision(const int x, const int y) { return x == m_x && y==m_y; }) and to store pointers to base in the vector (e.g. std::vector<GameObjectElement*> v). Then - miracle happens :) - calling v[i]->collision(x, y) will call the apropriate implementation in the descendant. Be sure to allocate elements with new and deallocate with delete - or just use shared_ptr<GameObjectElement> if you want to make it very simple in one cal.
If the selection of colission algorithm depends on the type of both instances, you are looking for a Double dispatch mechanism, which can be implemented in c++ using the Visitor pattern
The Wikipedia page actually has an example that covers collision of objects :)
https://en.wikipedia.org/wiki/Double_dispatch#Double_dispatch_is_more_than_function_overloading
I am newbie in OOP. Recently I have read about Liskov Substitution Principle.
In the code given below, Square class inherits Give_Area. Suppose Square class has something to do related to a square(like validity check). Give_Area gives area of square(4 vertex are on the perimeter of a circle) and area of a circle. So, If I am given a Radius, I've to print area of the circle and square(consists of vertexes placed on the perimeter of that circle). To get the area of a circle, I have used a parameter. But there is no parameter when getting area of square. Thus I have done overloading here.
#include<iostream>
#include<cmath>
using namespace std;
class Give_Area
{
public:
double Radius;
double Area(double pi)
{
return pi*Radius*Radius;
}
double Area()
{
double temp = sqrt(2.0)*Radius;
return temp*temp;
}
};
class Square : public Give_Area
{
public:
bool Validity()
{
//checking validity
}
};
int main()
{
Give_Area* area = new Square();
area->Radius = 3.0;
cout<< "Area of Circle: " << area->Area(3.14159) <<endl;
cout<< "Area of Square: " << area->Area() <<endl;
return 0;
}
My question is..
Is this overloading violating Liskov Substitution Principle?
If this code is violating, then would anybody please give me an example of an overloading which will not violate Liskov Substitution Principle?
I googled my query but have found nothing. :(
Thanks in advance.
The LSP
Liskov's Substitution Principle (or LSP) is about abstraction. Imagine a class Shape and two classes Square and Rectangle deriving from Shape. Now Shape has a (virtual) method getArea(). You would expect it to return the area that is covered by the (concrete, instanced!) shape regardless of what type it actually is. So if you call getArea() on a Shape instance, you don't care whether it is a rectangle, a square or any other shape you could think of.
The Answer
Without overloading there wouldn't even be the need for something like the LSP, i.e. the answer is no, overloading and LSP doesn't contradict.
The Design
On the other hand, as paxdiablo pointed out, applying LSP depends on the design. In terms of the example above this means, maybe for some reason you actually do care wether you have a rectangle or not. Well, in that case the LSP says you should think about your design.
Your Code
I have to admit at this point, I don't really get where your code is aiming at. There is a class Give_Area which calculates the area of a cirle depending on the value of, uh, pi. A second method calculates a square that has Radius as its diagonal? Then there is the Square class. If Validity() returns false, what would that mean? A degenerated square maybe? My suggestions is: Reconsider your design. Ask yourself "what are the classes and objects I want to deal with?" and "what are the real-world objects I want do model?"
The Counterexample
How to violate the LSP is demonstrated at Wikipedia (link above). I will try to make up a second example. Say you have a class Car with a method drive(). Deriving classes (RacingCar, Van, ...) could specify speed, accelaration, etc. When a car drives into water (deep water, a lake, the sea) the car will break and the next garage is called. Now you derive a class AmphibiousVehicle. This one doesn't break on water and the garage would be called with no use. Did you expect that? Maybe yes. But if not, depending on further context I would think about a class Vehicle which is the base of Car. It would have a method move(). And drive() which belongs still to Car would call move() and might call (again ;-)) the garage in case of trouble. And so on.
I am a decent procedural programmer, but I am a newbie to object orientation (I was trained as an engineer on good old Pascal and C). What I find particularly tricky is choosing one of a number of ways to achieve the same thing. This is especially true for C++, because its power allows you to do almost anything you like, even horrible things (I guess the power/responsibility adage is appropriate here).
I thought it might help me to run one particular case that I'm struggling with by the community, to get a feel for how people go about making these choices. What I'm looking for is both advice pertinent to my specific case, and also more general pointers (no pun intended). Here goes:
As an exercise, I am developing a simple simulator where a "geometric representation" can be of two types: a "circle", or a "polygon". Other parts of the simulator will then need to accept these representations, and potentially deal with them differently. I have come up with at least four different ways in which to do this. What are the merits/drawbacks/trade-offs of each?
A: Function Overloading
Declare Circle and Polygon as unrelated classes, and then overload each external method that requires a geometric representation.
B: Casting
Declare an enum GeometricRepresentationType {Circle, Polygon}. Declare an abstract GeometricRepresentation class and inherit Circle and Polygon from it. GeometricRepresentation has a virtual GetType() method that is implemented by Circle and Polygon. Methods then use GetType() and a switch statement to cast a GeometricRepresentation to the appropriate type.
C: Not Sure of an Appropriate Name
Declare an enum type and an abstract class as in B. In this class, also create functions Circle* ToCircle() {return NULL;} and Polygon* ToPolygon() {return NULL;}. Each derived class then overloads the respective function, returning this. Is this simply a re-invention of dynamic casting?
D: Bunch Them Together
Implement them as a single class having an enum member indicating which type the object is. The class has members that can store both representations. It is then up to external methods not to call silly functions (e.g. GetRadius() on a polygon or GetOrder() on a circle).
Here are a couple of design rules (of thumb) that I teach my OO students:
1) any time you would be tempted to create an enum to keep track of some mode in an object/class, you could (probably better) create a derived class for each enum value.
2) any time you write an if-statement about an object (or its current state/mode/whatever), you could (probably better) make a virtual function call to perform some (more abstract) operation, where the original then- or else-sub-statement is the body of the derived object's virtual function.
For example, instead of doing this:
if (obj->type() == CIRCLE) {
// do something circle-ish
double circum = M_PI * 2 * obj->getRadius();
cout << circum;
}
else if (obj->type() == POLY) {
// do something polygon-ish
double perim = 0;
for (int i=0; i<obj->segments(); i++)
perm += obj->getSegLength(i);
cout << perim;
}
Do this:
cout << obj->getPerimeter();
...
double Circle::getPerimeter() {
return M_PI * 2 * obj->getRadius();
}
double Poly::getPerimeter() {
double perim = 0;
for (int i=0; i<segments(); i++)
perm += getSegLength(i);
return perim;
}
In the case above it is pretty obvious what the "more abstract" idea is, perimeter. This will not always be the case. Sometimes it won't even have a good name, which is one of the reasons it's hard to "see". But, you can convert any if-statement into a virtual function call where the "if" part is replaced by the virtual-ness of the function.
In your case I definitely agree with the answer from Avi, you need a base/interface class and derived subclasses for Circle and Polygon.
Most probably you'll have common methods between the Polygon and Circle. I'd combine them both under an interface named Shape, for example(writing in java because it's fresher in my mind syntax-wise. But that's what I would use if I wrote c++ example. It's just been a while since I wrote c++):
public interface Shape {
public double getArea();
public double getCentroid();
public double getPerimiter();
}
And have both Polygon and Circle implement this interface:
public class Circle implements Shape {
// Implement the methods
}
public class Polygon implements Shape {
// Implement the methods
}
What are you getting:
You can always treat Shape as a generelized object with certain properties. You'll be able to add different Shape implementations in the future without changing the code that does something with Shape (unless you'll have something specific for a new Shape)
If you have methods that are exactly the same, you can replace the interface with abstract class and implement those (in C++ interface is just an abstract class with nothing implemented)
Most importantly (I'm emphesizing bullet #1) - you'll enjoy the power of polymorphism. If you use enums to declare your types, you'll one day have to change a lot of places in the code if you want to add new shape. Whereas, you won't have to change nothing for a new class the implements shape.
Go through a C++ tutorial for the basics, and read something like Stroustrup's "The C++ programming language" to learn how to use the language idiomatically.
Do not believe people telling you you'd have to learn OOP independent of the language. The dirty secret is that what each language understands as OOP is by no means even vaguely similar in some cases, so having a solid base in, e.g. Java, is not really a big help for C++; it goes so far that the language go just doesn't have classes at all. Besides, C++ is explicitly a multi-paradigm language, including procedural, object oriented, and generic programming in one package. You need to learn how to combine that effectively. It has been designed for maximal performance, which means some of the lower-bit stuff shows through, leaving many performance-related decisions in the hands of the programmer, where other languages just don't give options. C++ has a very extensive library of generic algorithms, learning to use those is required part of the curriculum.
Start small, so in a couple year's time you can chuckle fondly over the naïveté of your first attempts, instead of pulling your hair out.
Don't fret over "efficiency," use virtual member functions everywhere unless there is a compelling reason not to. Get a good grip on references and const. Getting an object design right is very hard, don't expect the first (or fifth) attempt to be the last.
First, a little background on OOP and how C++ and other languages like Java differ.
People tend to use object-oriented programming for several different purposes:
Generic programming: writing code that is generic; i.e. that works on any object or data that provides a specified interface, without needing to care about the implementation details.
Modularity and encapsulation: preventing different pieces of code from becoming too tightly coupled to each other (called "modularity"), by hiding irrelevant implementation details from its users.
It's another way to think about separation of concerns.
Static polymorphism: customizing a "default" implementation of some behavior for a specific class of objects while keeping the code modular, where the set of possible customizations is already known when you are writing your program.
(Note: if you didn't need to keep the code modular, then choosing behavior would be as simple as an if or switch, but then the original code would need to account for all of the possibilities.)
Dynamic polymorphism: like static polymorphism, except the set of possible customizations is not already known -- perhaps because you expect the user of the library to implement the particular behavior later, e.g. to make a plug-in for your program.
In Java, the same tools (inheritance and overriding) are used for solving basically all of these problems.
The upside is that there's only one way to solve all of the problems, so it's easier to learn.
The downside is a sometimes-but-not-always-negligible efficiency penalty: a solution that resolves concern #4 is more costly than one that only needs to resolve #3.
Now, enter C++.
C++ has different tools for dealing with all of these, and even when they use the same tool (such as inheritance) for the same problem, they are used in such different ways that they are effectively completely different solutions than the classic "inherit + override" you see in Java:
Generic programming: C++ templates are made for this. They're similar to Java's generics, but in fact Java's generics often require inheritance to be useful, whereas C++ templates have nothing to do with inheritance in general.
Modularity and encapsulation: C++ classes have public and private access modifiers, just like in Java. In this respect, the two languages are very similar.
Static polymorphism: Java has no way of solving this particular problem, and instead forces you to use a solution for #4, paying a penalty that you don't necessarily need to pay. C++, on the other hand, uses a combination of template classes and inheritance called CRTP to solve this problem. This type of inheritance is very different from the one for #4.
Dynamic polymorphism: C++ and Java both allow for inheritance and function overriding, and are similar in this respect.
Now, back to your question. How would I solve this problem?
It follows from the above discussion that inheritance isn't the single hammer meant for all nails.
Probably the best way (although perhaps the most complicated way) is to use #3 for this task.
If need be, you can implement #4 on top of it for the classes that need it, without affecting other classes.
You declare a class called Shape and define the base functionality:
class Graphics; // Assume already declared
template<class Derived = void>
class Shape; // Declare the shape class
template<>
class Shape<> // Specialize Shape<void> as base functionality
{
Color _color;
public:
// Data and functionality for all shapes goes here
// if it does NOT depend on the particular shape
Color color() const { return this->_color; }
void color(Color value) { this->_color = value; }
};
Then you define the generic functionality:
template<class Derived>
class Shape : public Shape<> // Inherit base functionality
{
public:
// You're not required to actually declare these,
// but do it for the sake of documentation.
// The subclasses are expected to define these.
size_t vertices() const;
Point vertex(size_t vertex_index) const;
void draw_center(Graphics &g) const { g.draw_pixel(shape.center()); }
void draw_outline()
{
Derived &me = static_cast<Derived &>(*this); // My subclass type
Point p1 = me.vertex(0);
for (size_t i = 1; i < me.vertices(); ++i)
{
Point p2 = me.vertex(1);
g.draw_line(p1, p2);
p1 = p2;
}
}
Point center() const // Uses the methods above from the subclass
{
Derived &me = static_cast<Derived &>(*this); // My subclass type
Point center = Point();
for (size_t i = 0; i < me.vertices(); ++i)
{ center += (center * i + me.vertex(i)) / (i + 1); }
return center;
}
};
Once you do that, you can define new shapes:
template<>
class Square : public Shape<Square>
{
Point _top_left, _bottom_right;
public:
size_t vertices() const { return 4; }
Point vertex(size_t vertex_index) const
{
switch (vertex_index)
{
case 0: return this->_top_left;
case 1: return Point(this->_bottom_right.x, this->_top_left.y);
case 2: return this->_bottom_right;
case 3: return Point(this->_top_left.x, this->_bottom_right.y);
default: throw std::out_of_range("invalid vertex");
}
}
// No need to define center() -- it is already available!
};
This is probably the best method since you most likely already know all possible shapes at compile-time (i.e. you don't expect the user will write a plug-in to define his own shape), and thus don't need any of the whole deal with virtual. Yet it keeps the code modular and separates the concerns of the different shapes, effectively giving you the same benefits as a dynamic-polymorphism approach.
(It is also the most efficient option at run-time, at the cost of being a bit more complicated at compile-time.)
Hope this helps.
I had a really long post on this and decided it can be summed up much shorter. Canonically speaking, is it better to include a data member inside of a class as opposed to inheriting it? I found I can implement identical functions either way, but don't really know what caveats I should be aware of.
Code example
#include "KClass.h"
class KPC : public KCharacter {
private:
KClass MyClass;
};
versus
class KClass : public KCharacter {
};
class KPC : public KClass {
};
In the first example, anytime I needed something from the KClass data, I could access it via MyClass->
In the second class, class KPC would just directly access them since it would inherit the data members.
For specifics to my problem I guess I should detail the class' function.
D&D format. Each character has a class which would determine: weapon/armor proficiencies, bonus defenses, special ability i.e. defender has mark.
So to me, it made sense to inherit it. However, is class a more specific PC or is PC a specific kind of class? There are so many PCs in a game that aren't a specific class, actually class should inherit PC on that concept sense it's more 'specialized' form of a PC. So would I want to structure it in a way of KClass : KPC ?
It seemed easier to implement a Has-A at first, but now I'm second guessing it. Hence the reason why I'm here, asking this question.
Generally speaking, composition is better than inheritance. But it depends on what exactly you want to do. For the most part think:
IS A -> inheritance
HAS A -> composition
Inherit when you want/need to extend a base class. If you just need to use another class, just have an instance of it with the other class.
Side note, composition and aggregation are basically the same thing. Conceptually slightly different, in code, the same thing.
It's a matter of design and what you are trying to model. Scott Meyers' Effective C++ will note that public inheritance (the second example) models 'is-a', whereas composition (the first example) models 'is-implemented-in-terms-of' or 'has-a'. So, for your example, you should decide what role KClass is playing and which of these philosophies makes more sense. Just looking at the names KCharacter, KClass, and KPC, it's hard for me to tell their purposes.
It really depends on what you are trying to do. Yes, both achieve mechanically similar things, but the rule is "is-a" or "has-a" for deciding which way to go.
If KPC really "is-a" form of KClass, then you should use inheritance. This means that you are looking to solve a polymorphic problem - you have several items that are similar:
class AeroPlaneBase
{
...
};
class JetPlane : public AeroPlaneBase
{
...
};
class PropellerPlane : public AeroPlaneBase
{
...
};
class GliderPlane : public AeroPlaneBase
{
};
All of these planes can do similar things - but they behave slightly differently, so they need a different class to describe their behaviour.
Now, each plane will have zero or more "engines", so the class may have a "has-a" relationship to a PlaneEngine class. The glider, which is an engineless plane doesn't have any engine, the JetPlane can have 8, perhaps...
Likewise, in a roleplaying game, a player "is-a" Character (which is also the baseclass for Monster and the different derived forms of that), say, but "has-a" relationship with the Weapon class. The Character isn't a type of Weapon, but it has a weapon.
Conceptual
The concept of classes and objects is usually used to model "real" things. But let's put the cart before the horse.
The transfer of the inheritance concept to the real world would be (like others said) an IS A-relation.
A TFT is a screen
A Fox is an Animal
...
The composition is, in contrast, usually considerd as HAS A-relation.
A PC has a CPU
A knife has a blade
...
So if you want to model the latter in object-oriented programming, make use of composition. In case of the former concept, use inheritance.
Examples
Composition > Inheritance
Examples always tend to come naturally to me. So I'll try to illustrate it a bit further. (No encapsulation here, sorry. ;))
Consider motorvehicles, respectively cars. The tend to have an engine, which has a specific sound.
struct Engine
{
void sound (void) const { std::cout << "BROOOM" << std::endl; }
void open_valve (void) { /* ... */ }
};
Engines also can perform certain engine-specific tasks.
Now we can have both specified options to include the engine into a car: inheritance or composition.
struct Car_A : public Engine { };
At the first moment, this seems appropriate. We don't need to reprovide sound() since a car (in the first approximation) just sounds like an engine.
Car_A a_car;
a_car.sound(); // mycar sounds like a car!
But the noise is not very realistic: No tread noise, no air draft. So we can just shadow the underlying method and define:
struct Car_A : public Engine
{
void sound (void) const
{
std::cout << "tread noise + air draft" << std::endl;
Engine::sound();
}
};
We still have a slight problem.
a_car.open_valve(); // ?
The concept of valves is part of the engine but not part of the car but we can use this method on the car.
The car has an engine but it isn't one.
We could switch to private inheritance now but the method would still be present, although not accessible.
Another (less conceptual) issue can be seen when using pointers of the types:
Engine * pointer_to_engine(new Car_A); // works
An engine that actually is a car? "(Suspected) Engines" exhibiting car behaviour and vice versa? Well that doesn't look like the way to do things here.
Let's look at composition instead:
struct Car_B
{
void sound (void) const
{
std::cout << "tread noise + air draft" << std::endl;
engine.sound();
}
void open_door (void) { /* ... */ }
Engine engine;
};
That's how things are supposed to be: A car that has a[n] (member) engine that sounds like an engine and contributes to the sound of the car and no methods are present in the car that are not part of the concept of a car.
Car_B b_car;
b_car.sound(); // still sounds like a car!
b_car.engine.open_valve(); // meaningful for an engine!
Here we have a case where composition is superior.
The "real" situation is modeled.
All concepts keep their validity. (No unintended behaviour.)
Inheritance > Composition
Now we add another Concept in our example: a vehicle.
struct Wheel {};
struct Motorvehicle
{
virtual void sound (void) const { engine.sound(); }
Engine engine;
std::vector<Wheel> wheels;
};
A motorvehicle is driven by an engine, so it knows to emmit engine sound.
However, the abstract vehicle has no clue of how many wheels its concrete objects will have have (motorcycle? car?) or how its shape is formed, so it can't tell anything about tread noise and air draft.
This time we look at composition first (miracle miracle...):
struct Car_C
{
void sound (void) const
{
std::cout << "tread noise + air draft" << std::endl;
vehicle.sound();
}
Motorvehicle vehicle;
};
Looks legit, doesn't it?
Car_C c_car;
c_car.sound(); // correct sound!
c_car.vehicle.sound(); // what the hell is "the vehicle of a car"?
c_car.wheels.... // error the car has no wheels?!
"Pretending" that wheels are part of the car will require us to add an additional function for our car. If we use inheritance instead, this coherency comes from scratch.
struct Car_D
: public Motorvehicle
{
void sound (void) const
{
std::cout << "tread noise + air draft" << std::endl;
Motorvehicle::sound();
}
};
The observable behaviour of Car_D is more like you would expect it to be.
Car_D d_car;
d_car.sound(); // correct sound!
d_car.wheels.[...] // valid, our car has wheels!
Conclusion
The consideration, whether to use inheritance or composition is not always as easy as in my examples but you should try to weight up and choose the concept that performas better in reflecting the desired behaviour.
If the designated base class describes an abstract generalization of the derived class, this is a good hint for inheritance.
I am working on a Ray Tracing task, here is the problematic source:
class Geometry
{
public:
virtual RayTask *intersectionTest(const Ray &ray) = 0;
};
class Sphere : public Geometry
{
public:
RayTask *intersectionTest(const Ray &ray);
};
class BoundingVolume
{
public:
virtual bool intersectionTest(const Ray &ray) = 0;
};
class BoundingSphere : public Sphere, BoundingVolume
{
public:
bool intersectionTest(const Ray &ray) // I want this to be inherited from BoundingVolume
{
return Sphere::intersectionTest(ray) != NULL; // use method in Sphere
}
};
source above can not compile, error information:
error: conflicting return type specified for ‘virtual bool BoundingSphere::intersectionTest(const Ray&)’
error: overriding ‘virtual RayTask Sphere::intersectionTest(const Ray&)
I want to implement BoundingSphere::intersectionTest using method in Sphere, so I need to inherit from both BoundingVolume and Sphere. but due to inherit functions that has the same parameter list with different return type, things messed up...
I do not want to duplicate codes with the same functionality...
could any one give me a solution?...
The compiler is attempting to override two virtual methods with different return types, which isn't allowed: how would the compiler know how much memory to allocate for a function call if it doesn't know what the return type will be? The two methods cannot have the same names; try changing one to a more suitable meaning.
If you feel that these names best represent the meanings of the actions they both provide (which I'm not sure of), I would also suggest that you consider your hierarchies carefully. Is a spherical BoundingVolume really a Sphere? Perhaps not: it's implemented in terms of Sphere (private inheritance, doesn't solve your problem), or it has a Sphere (composition, would solve your problem in this simple case). The latter case, though, might present problems for move complex classes, where you want a BoundingSphere to have all the methods of Sphere. Or, perhaps, do you need to differentiate between BoundingVolumes and normal Geometrys?
Another solution to the problem would be to use non-member functions for one of these hierarchies, with Koenig lookup (the type of the argument) calling the proper version. I can't say without really knowing what your hierarchies look like. But do consider your design: if you have the same-named operation giving you back completely different semantic results, is the operation properly named/designed?