The class should have one, and only one, reason to change. A class have hight cohesion if it’s behaviours are coselly related.
class Integration {
private:
bool PaseXML(Document doc);
public:
bool ImportData(Document doc);
};
bool Integration::ImportData(Document doc){
bool result = ParseXML(doc); // (2)
ShowGUIDialogBox(result); // (1)
return result;
}
/*
Problems:
(1) - This class has two function: parse a XML file and present on GUI the result
(2) - The paser works for XML files, and it we want to parse a JSON
file this will lead to a major refactor
Possible Solution:
(1) declouple classes by responsability adding a new levels os indirection
*/
class Integration {
private:
bool PaseXML(Document doc);
IParser* _parser;
GUI* _gui;
public:
bool ImportData(Document doc);
};
bool Integration::ImportData(Document doc){
bool result = _parser->importXMlFile(doc); //(1)
_gui->printMessage(result); //(1)
return result;
}
You should be able to extend a class behaviour, without modifying it
enum FileType
{
XML,
JSON
};
class Integration {
public:
void setFileType(FileType ftype);
void Import();
private:
FileType _fileType;
};
void Integration:setFileType(FileType ftype){
_fileType = ftype;
}
void Integration::Import() {
switch(_ftype) { //(1)
case XML:
ParseXML(...);
break;
case JSON:
ParseJSON(...);
break;
\\...
}
}
/*
Problems:
Integration class is not closed to modification. Every time we need to import a
new file type, we have to add a new enums, add new parser ...
Possible solution:
Use inheritance and polymorphism to create multiple parsers with specific
implementations acording the file type to import
*/
class IParser {
public:
virtual void Import(...) = 0;
};
class XMLParser: public IParser {
public:
XMLParser() = default;
virtual void Import(...) override;
};
class JSONParser: public IParser {
public:
JSONParser() = default;
virtual void Import(...) final;
};
class Integration {
public:
Integration(shared_prt<IParser> parser)
void setFileType(FileType ftype);
void Import();
private:
shared_prt<IParser> _parser;
};
void Integration::Import() {
parser->Import(...);
}
// How to use it
Integration integration(make_shared<XMLparser>());
// or
Integration integration(make_shared<JSONparser>());
Derived classes must be substitutable for their base classes. A nice example using C++ would be using pointers, functions that use pointers from base class can use objects from derived classes without knowing it.
class Bird {
public:
virtual void setLocation(double longitude, double latitude) = 0;
virtual void setAltitude(double altitude) = 0;
};
class Penguin: public Bird {
virtual void setLocation(double longitude, double latitude) override;
virtual void setAltitude(double altitude) override;
};
virtual Penguin::setAltitude(double altitude)
{
//altitude can't be set because penguins can't fly
//this function does nothing
}
/*
Problems:
Penguins can't fly so it's an Object Oriented trap !!
Solution:
In C++ we can use multiple inheritance
*/
class Bird {
public:
virtual void draw() = 0;
virtual void setLocation(double longitude, double latitude) = 0;
};
class FlightfulBird : public Bird {
public:
virtual void setAltitude(double altitude) = 0;
};
class Penguin: public Bird {
// ...
};
class Eagle: public Bird, public FlightfulBird {
// ...
}
source code based on: SOLID Class Design: The Liskov Substitution Principle
Make fine grained interfaces that are client specific. Clients should not be forced to depend on ippon interfaces that they not use.
class ISmartDevice {
virtual void Print() = 0;
virtual void Fax() = 0;
virtual void Scan() = 0;
};
class MultiPrinterDevice(): ISmartDevice {
virtual void Print() override {
// do something ...
}
virtual void Fax() override {
// do something ...
}
virtual void Scan() override {
// do something ...
}
};
class EconomicPrinterDevice(): ISmartDevice {
virtual void Print() {
// do something ...
}
virtual void Fax() { // (1)
// do nothing ...
}
virtual void Scan() {
// do something ...
}
};
/*
Problem:
(1) If we inherit from ISmartDevice we have to override all methods even it
we don't use it. What leads to empty methods, bad returns code, ....
Solution:
Had new levels of indirections, e.g using multiple inheritance
*/
class IPrinterDevice {
virtual void Print() = 0;
};
class IFaxDevice {
virtual void Fax() = 0;
};
class IScannertDevice {
virtual void Scan() = 0;
};
class MultiPrinterDevice: public IPrinterDevice, public IFaxDevice, public IScannerDevice{
//...
};
class EconomicPrinterDevice: public IPrinterDevice, public IScannerDevice{
//...
};
Depend on abstractions, not on concretions. High modules should not depend upon low levels
class Button{
private:
unique_ptr<Light> _light;
public:
void PressOn(){
_light.TurnOn(); //(1)
}
};
/*
Problem:
(1) - Class button should not depend on upper class Light
Solution:
Add a middle man, and Interface that can had a new level of indirection
*/
class IDevice {
public:
virtual void TurnOn() = 0;
virtual void TurnOff() = 0;
};
class Light: public IDevice {
public:
virtual void TurnOn() {
// Do Something ...
}
virtual void TurnOff() {
// Do Something ...
}
};
class Button {
private:
unique_ptr<Device> _device;
public:
void PressOn() {
_device.TurnOn();
}
};
The granule of reuse is the granule of release
Classes that change together are packed together
Classes that are used together are packed together
The dependency graph of packages must have no cycles
Depend in direction of stability
Abstractness increases with stability