项目作者: WeiChienHsu

项目描述 :
:rocket: S.O.L.I.D. principle Implementations || :book: Design Patterns - :hammer: Creational :hammer: Structural :hammer: Behavioral
高级语言: C++
项目地址: git://github.com/WeiChienHsu/DesignPattern.git
创建时间: 2018-09-29T01:48:44Z
项目社区:https://github.com/WeiChienHsu/DesignPattern

开源协议:

下载


Design Pattern in C++

Common architectural approaches.

SOLID Design Principle

Creational Patterns

Structural Patterns

Behavioral Patterns

Reference Credits


SOLID Design Principle

Single Responsibility Principle

  • A class should only have one reason to change.
  • Separation of concerns - different classes handling different, independent tasks/problems.

Open Closed Principle

  • Classes should be open for extension but closed for modification.

Liskov Substitution Principle

  • You should be able to substitute a base type for a subtype.

Interface Segregation Principle

  • Don’t put too much into an interface; split into separate interfaces
  • YAGNI - You Ain’t Going to Need It

Dependency Inversion Principle

  • High-Level modules should not depend upon low-level ones; use abstraction.

Single Responsibility Principle

A class should have single reason to change or take only one responsibility.

All of the contents of a single class are tightly coupled together, since the class itself is a single unit that must either be entirely used or not at all (discounting static methods and data for the moment). When other classes make use of a particular class, and that class changes, the depending classes must be tested to ensure they continue to function correctly with the new behavior of the class.

We define each responsibility of a class as a reason for change. If you can think of more than one motivation for changing a class, it probably has more than one responsibility. When these axes of change occur, the class will probably need to have different aspects of its behavior changed, at different times and for different reasons.

Some examples of responsibilities to consider that may need to be separated include:

  • Persistence
  • Validation
  • Notification
  • Error Handling
  • Logging
  • Class Selection / Instantiation
  • Formatting
  • Parsing
  • Mapping
  1. struct PersistenceManager
  2. {
  3. static void save(const Journal& j, const string& filename)
  4. {
  5. ofstream ofs(filename);
  6. for(auto &e : j.entries) {
  7. ofs << e << endl;
  8. }
  9. }
  10. };

Back to SOLID Principle

Open Closed Principle

Software entities (classes, modules, methods, etc.) should be open for extension, but closed for modification.
Avoid to jump into the code you have already writen.

  1. /* Specification Interface */
  2. template <typename T> struct Specification
  3. {
  4. virtual bool is_satisfied(T* item) = 0;
  5. /* Should be defined through the inheritance */
  6. AndSpecification<T> operator&&(Specification<T> && other)
  7. {
  8. return AndSpecification<T>(*this, other);
  9. }
  10. };
  11. /* Filter Interface */
  12. template <typename T> struct Filter
  13. {
  14. /* Pure Virtual Interface */
  15. virtual vector<T*> filter(vector<T*> items,
  16. Specification<T> &spec) = 0;
  17. };
  18. struct BetterFilter : Filter<Product>
  19. {
  20. vector<Product *> filter(vector<Product*> items, Specification<Product> &spec)
  21. override {
  22. vector<Product*> result;
  23. for(auto &item : items)
  24. if(spec.is_satisfied(item))
  25. result.push_back(item);
  26. return result;
  27. }
  28. };
  29. struct ColorSpecification : Specification<Product>
  30. {
  31. Color color;
  32. ColorSpecification(Color color) : color(color) {}
  33. bool is_satisfied(Product *item) override {
  34. return item -> color == color;
  35. }
  36. };
  37. struct SizeSpecification : Specification<Product>
  38. {
  39. Size size;
  40. explicit SizeSpecification(const Size size)
  41. : size { size }
  42. }
  43. bool is_satisfied(Product *item) override {
  44. return item -> size == size;
  45. }
  46. };
  47. template <typename T> struct AndSpecification : Specification<T>
  48. {
  49. Specification<T> &first;
  50. Specification<T> &second;
  51. AndSpecification(Specification<T> &first,
  52. Specification<T> &second) : first(first), second(second) {}
  53. bool is_satisfied(T *item) override {
  54. return first.is_satisfied(item) && second.is_satisfied(item);
  55. }
  56. };
  1. int main()
  2. {
  3. Product apple{"Apple", Color::green, Size::small};
  4. Product tree{"Tree", Color::green, Size::large};
  5. Product house{"House", Color::blue, Size::large};
  6. vector<Product*> items {&apple, &tree, &house};
  7. /* Poor method to write a product filter */
  8. ProductFilter pf;
  9. auto green_things = pf.by_color(items, Color::green);
  10. for(auto & item : greem_things)
  11. cout << item->name << " is " << item->color << endl;
  12. /* By implementing Specification and Filter Interfaces */
  13. BetterFilter bf;
  14. ColorSpecification green(Color::green);
  15. for(auto & item : bf.filter(items, green))
  16. count << item -> name << " is " << item -> color << endl;
  17. SizeSpecification large(Size::large);
  18. AndSpecification<Product> green_and_large(green, large);
  19. /* Same method of combining both SizeSpecification and ColorSpecification */
  20. /* By overriding the operator "&&" in the Specification class */
  21. auto spec = ColorSpecification(Color::green)
  22. && SizeSpecification(Size::large);
  23. for(auto & item : bf.filter(items, spec))
  24. count << item -> name << " is green and large." << endl;
  25. return 0;
  26. }

Back to SOLID Principle

Liskov Substitution Principle

Subtypes must be substitutable for their base types.

When this principle is violated, it tends to result in a lot of extra conditional logic scattered throughout the application, checking to see the specific type of an object. This duplicate, scattered code becomes a breeding ground for bugs as the application grows.

  1. Inheritance:
  2. One object can be designed to inherit from another if it always has an
  3. IS-SUBSTITUTABLE-FOR relationship with the inherited object.

A common code smell that frequently indicates an LSP violation is the presence of type checking code within a code block that should be polymorphic. For instance, if you have a foreach loop over a collection of objects of type Foo, and within this loop there is a check to see if Foo is in fact Bar (subtype of Foo), then this is almost certainly an LSP violation. If instead you ensure Bar is in all ways substitutable for Foo, there should be no need to include such a check.

  1. /* Valid the case in Square */
  2. void process(Rectangle& r)
  3. {
  4. int w = r.getWidth();
  5. r.setHeight(10); /* Also set Width as 10 in Square */
  6. cout << "excepted area = " << (w*10)
  7. << " , got " << r.area() << endl;
  8. }
  9. int main() {
  10. Rectangle r{3, 4};
  11. process(r); /* Excepted 40, got 40 */
  12. Square sq{5};
  13. process(sq); /* Excepted 50, got 100 */
  14. return 0;
  15. }

Back to SOLID Principle

Interface Segregation Principle

Clients should not be forced to depend on methods that they do not use.

Interfaces should belong to clients, not to libraries or hierarchies. Application developers should favor thin, focused interfaces to “fat” interfaces that offer more functionality than a particular class or method needs.

Another benefit of smaller interfaces is that they are easier to implement fully, and thus less likely to break the Liskov Substitution Principle by being only partially implemented.

Bad Implementation: We gave client an interface that client could directly implement the method they need it.

  1. struct IMachine
  2. {
  3. virtual void print(Document &doc) = 0;
  4. virtual void scan(Document &doc) = 0;
  5. virtual void fax(Document &doc) = 0;
  6. };
  7. struct MFP : IMachine
  8. {
  9. void print(Document &doc) override {
  10. // DO
  11. }
  12. void scan(Document &doc) override {
  13. // NULL VALUE
  14. }
  15. void fax(Document &doc) override {
  16. // NULL VALUE
  17. }
  18. };
  19. struct Scanner : IMachine
  20. {
  21. void print(Document &doc) override {
  22. // NULL VALUE
  23. }
  24. void scan(Document &doc) override {
  25. // DO
  26. }
  27. void fax(Document &doc) override {
  28. // NULL VALUE
  29. }
  30. };

Make Iterfaces smaller

  1. struct IPrinter
  2. {
  3. virtual void print(Document &doc) = 0;
  4. };
  5. struct IScanner
  6. {
  7. virtual void scan(Document &doc) = 0;
  8. };
  9. struct IFax
  10. {
  11. virtual void fax(Document &doc) = 0;
  12. };
  13. struct Printer : IPrinter {};
  14. struct Scanner : IScanner {};
  15. struct IMachine : IPrinter, IScanner {};
  16. struct Machine : IMachine
  17. {
  18. IPrinter& printer;
  19. IScanner& scanner;
  20. Machine(IPrinter &printer, IScanner &scanner) :
  21. printer(printer), scanner(scanner) {}
  22. void print(Document &doc) override {
  23. printer.print(doc);
  24. }
  25. void scan(Document &doc) override {
  26. scanner.scan(doc);
  27. }
  28. };

Back to SOLID Principle

Dependency Inversion Principle

  • High-Level Modules should not depend on Low-Level Modules. Both should depend on abstractions.

  • Abstractions should not depend on details. Details should depend on abstractions.

  • Should not depend on details of some body else’s implementation.

If the low-level module Relationships would like to make vector of relations becomes private or other type of data, then the high-level module Research would be crushed.

  1. struct Relationships /* Low Level Modules */
  2. {
  3. vector<tuple<Person, Relationship, Person>> relations;
  4. void add_parent_and_child(const Person& parent, const Person& child)
  5. {
  6. relations.push_back({parent, Relationship :: parent, child});
  7. relations.push_back({child, Relationship :: child, parent});
  8. }
  9. };
  10. struct Research /* High Level Modules */
  11. {
  12. Research(Relationships& relationships)
  13. {
  14. auto& relations = relationships.relations;
  15. for(auto &&[first, rel, second] : relations)
  16. {
  17. if(first.name == "John" && rel == Relationship::parent)
  18. {
  19. cout << "John has a child called" << second.name << endl;
  20. }
  21. }
  22. }
  23. };
  24. int main() {
  25. Person parent{"John"};
  26. Person child1{"Chris"}, child2{"Matt"};
  27. Relationships rs;
  28. rs.add_parent_and_child(parent, child1);
  29. rs.add_parent_and_child(parent, child2);
  30. Research _(rs);
  31. return 0;
  32. }

Have dependency on abstraction RelationshipBrowser

  1. struct RelationshipBrowser
  2. {
  3. virtual vector<Person> find_all_children_of(const string& name) = 0;
  4. };
  5. struct Relationships : RelationshipBrowser /* Low Level Modules */
  6. {
  7. vector<tuple<Person, Relationship, Person>> relations;
  8. void add_parent_and_child(const Person& parent, const Person& child)
  9. {
  10. relations.push_back({parent, Relationship :: parent, child});
  11. relations.push_back({child, Relationship :: child, parent});
  12. }
  13. /* Implement the RelationshipBrowser */
  14. vector<Person> find_all_children_of(const string &name) override {
  15. vector<Person> result;
  16. for(auto &&[first, rel, second] : relations)
  17. {
  18. if(first.name == name && rel == Relationship::parent)
  19. {
  20. result.push_back(second);
  21. }
  22. }
  23. return result;
  24. }
  25. };
  26. struct Research /* Have dependency on abstraction RelationshipBrowser */
  27. {
  28. Research(RelationshipBrowser& browser)
  29. {
  30. for(auto& child : browser.find_all_children_of("John"))
  31. {
  32. cout << "John has a child called " << child.name << endl;
  33. }
  34. }
  35. };

Back to SOLID Principle


Creational Patterns

Builder

Abstract Factories

Factories

Prototype

Singleton

Builder

將一個複雜的構建過程與其具表示細節相分離,使得同樣的構建過程可以創建不同的表示。

  • Some objects are simple and can be created in a single constructor call.
  • Other objects require a lot of ceremony to create.
  • Having an object with 10 constructor arguments is not productive.
  • Instead, opt for piecewise construction.
  • Builder provides an API for constructing an object step-by-step
  1. struct HtmlBuilder
  2. {
  3. HtmlElement root;
  4. HtmlBuilder(string root_name)
  5. {
  6. root.name = root_name;
  7. }
  8. /* Fluent Interface : Return a Reference */
  9. HtmlBuilder& add_child(string child_name, string child_text)
  10. {
  11. HtmlElement e{ child_name, child_text };
  12. root.elements.emplace_back(e);
  13. return *this;
  14. }
  15. /* Fluent Interface : Return a Pointer */
  16. HtmlBuilder* add_child_2(string child_name, string child_text)
  17. {
  18. HtmlElement e{ child_name, child_text };
  19. root.elements.emplace_back(e);
  20. return this;
  21. }
  22. string str() const {return root.str();}
  23. };

Builder Coding Exercise

  1. #include <string>
  2. #include <vector>
  3. #include <ostream>
  4. using namespace std;
  5. struct Field
  6. {
  7. string name, type;
  8. Field(const string& name, const string& type)
  9. : name{name},
  10. type{type}
  11. {
  12. }
  13. friend ostream& operator<<(ostream& os, const Field& obj)
  14. {
  15. return os << obj.type << " " << obj.name << ";";
  16. }
  17. };
  18. struct Class
  19. {
  20. string name;
  21. vector<Field> fields;
  22. friend ostream& operator<<(ostream& os, const Class& obj)
  23. {
  24. os << "class " << obj.name << "\n{\n";
  25. for (auto&& field : obj.fields)
  26. {
  27. os << " " << field << "\n";
  28. }
  29. return os << "};\n";
  30. }
  31. };
  32. class CodeBuilder
  33. {
  34. Class the_class;
  35. public:
  36. CodeBuilder(const string& class_name)
  37. {
  38. the_class.name = class_name;
  39. }
  40. CodeBuilder& add_field(const string& name, const string& type)
  41. {
  42. the_class.fields.emplace_back(Field{ name, type });
  43. return *this;
  44. }
  45. friend ostream& operator<<(ostream& os, const CodeBuilder& obj)
  46. {
  47. return os << obj.the_class;
  48. }
  49. };
  50. int main()
  51. {
  52. auto cb = CodeBuilder{"Person"}.add_field("name", "string").add_field("age", "int");
  53. ostringstream oss;
  54. oss << cb;
  55. auto printed = oss.str();
  56. }

Back to Creational Patterns


Factories

不同條件下創建不同實例。

A component responsibile solely for the wholesale (not piecewise) creation of objects.

  • Object creation logic becomes too convoluted.
  • Constructor is not descriptive. (Cannot overload with smae sets of arguments with different names)
  • Object creation(non-piecewise, unlike Builder) can be outsourced to.
  • A separate funciton (Factory Method)
  • That may exist in a separate class (Factory)

Dont allow to redeclare the constructor with same types of arguments.

  1. struct Point
  2. {
  3. float x, y;
  4. Point(float x, float y) : x(x), y(y) {}
  5. Point(float rho, float theta) {
  6. }
  7. };

Instead we use anotehr ENUM class to represent PointType

  1. struct Point
  2. {
  3. float x, y;
  4. //!
  5. //! \param a this is either x or tho
  6. //! \param b this is either y or theta
  7. //! \param type
  8. Point(float a, float b, PointType type = PointType::cartesian)
  9. {
  10. if(type == PointType::cartesian)
  11. {
  12. x = a;
  13. y = b;
  14. } else {
  15. x = a * cos(b);
  16. y = b * sin(b);
  17. }
  18. }
  19. };

We still can opt to make our users know much clearly about the Interfaces

  1. struct Point
  2. {
  3. Point(float x, float y) : x(x), y(y) {}
  4. public:
  5. float x, y;
  6. static Point NewCartesian(float x, float y)
  7. {
  8. return {x, y};
  9. }
  10. static Point NewPolar(float r, float theta)
  11. {
  12. return {r*cos(theta), r*sin(theta)};
  13. }
  14. friend ostream &operator<<(ostream &os, const Point &point) {
  15. os << "x: " << point.x << " y: " << point.y;
  16. return os;
  17. }
  18. };

Still can improve by add a Factory but inteads of adding PointFactory as a friend class into Point (no valid in Open-Close Principle), we can make all variable and mehtods in Point Public

  1. struct Point
  2. {
  3. public:
  4. float x, y;
  5. Point(float x, float y) : x(x), y(y) {}
  6. friend ostream &operator<<(ostream &os, const Point &point) {
  7. os << "x: " << point.x << " y: " << point.y;
  8. return os;
  9. }
  10. };
  11. struct PointFactory
  12. {
  13. static Point NewCartesian(float x, float y)
  14. {
  15. return {x, y};
  16. }
  17. static Point NewPolar(float r, float theta)
  18. {
  19. return {r*cos(theta), r*sin(theta)};
  20. }
  21. };

Inner Factory : Make user have clear API to know what they should do

  1. class Point
  2. {
  3. Point(float x, float y) : x(x), y(y) {}
  4. class PointFactory
  5. {
  6. PointFactory() {}
  7. public:
  8. static Point NewCartesian(float x, float y)
  9. {
  10. return { x,y };
  11. }
  12. static Point NewPolar(float r, float theta)
  13. {
  14. return{ r*cos(theta), r*sin(theta) };
  15. }
  16. };
  17. public:
  18. float x, y;
  19. static PointFactory Factory;
  20. friend ostream &operator<<(ostream &os, const Point &point) {
  21. os << "x: " << point.x << " y: " << point.y;
  22. return os;
  23. }
  24. };
  25. int main()
  26. {
  27. auto p = Point::Factory.NewCartesian(2, 3);
  28. cout << p << endl;
  29. return 0;
  30. }

Back to Creational Patterns


Abstract Factories

Back to Creational Patterns


Prototype

通過拷貝原型創建新的對象。

Back to Creational Patterns


Singleton

保證一個類僅有一個實例。

Back to Creational Patterns


Structural Patterns

Adapter

Bridge

Composite

Decorator

Facade

Flyweight

Proxy


Adapter

使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。

Back to Structural Patterns


Bridge

兩個維度獨立變化,依賴方式實現抽象與實現分離:需要一個作為橋接的接口/抽象類,多個角度的實現類依賴注入到抽象類,使它們在抽象層建立一個關聯關係。

Back to Structural Patterns


Composite

用戶對單個對象和組合對象的使用具有一致性的統一接口。

Back to Structural Patterns


Decorator

保持接口,增強性能:修飾類繼承被修飾對象的抽象父類,依賴被修飾對象的實例(被修飾對象依賴注入),以實現接口擴展。

Back to Structural Patterns


Facade

在客戶端和複雜系統之間再加一層,這一次將調用順序、依賴關係等處理好。即封裝底層實現,隱藏系統的複雜性,並向客戶端提供了一個客戶端可以訪問系統的高層接口

Back to Structural Patterns


Flyweight

享元工廠類控制;HashMap實現緩衝池重用現有的同類對象,如果未找到匹配的對象,則創建新對象

Back to Structural Patterns


Proxy

為其他對象提供一種代理以控制對這個對象的訪問:增加中間層(代理層),代理類與底層實現類實現共同接口,並創建底層實現類對象(底層實現類對象依賴注入代理類),以便向外界提供功能接口。

Back to Structural Patterns


Behavioral Patterns

Chain of Responsibility

Command

Interpreter

Iterator

Mediator

Memento

Observer

State

Stategy

Template Method

Visitor

Chain of Responsibility

攔截的類都實現統一接口,每個接收者都包含對下一個接收者的引用。將這些對象連接成一條鏈,並且沿著這條鏈傳遞請求,直到有對象處理它為止。

Back to Behavioral Patterns


Command

將”行為請求者”與”行為實現者”解耦:調用者依賴命令,命令依賴接收者,調用者Invoker→命令Command→接收者Receiver。

Back to Behavioral Patterns


Interpreter

給定一個語言,定義它的文法表示,並定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子。

Back to Behavioral Patterns


Iterator

集合中含有迭代器:分離了集合對象的遍歷行為,抽象出一個迭代器類來負責,無須暴露該對象的內部表示。

Back to Behavioral Patterns


Mediator

對象與對象之間存在大量的關聯關係,將對象之間的通信關聯關係封裝到一個中介類中單獨處理,從而使其耦合鬆散,可以獨立地改變它們之間的交互。

Back to Behavioral Patterns


Memento

通過一個備忘錄類專門存儲對象狀態。客戶通過備忘錄管理類管理備忘錄類。

Back to Behavioral Patterns


Observer

一對多的依賴關係,在觀察目標類里有一個 ArrayList 存放觀察者們。當觀察目標對象的狀態發生改變,所有依賴於它的觀察者都將得到通知,使這些觀察者能夠自動更新。(即使用推送方式)

Back to Behavioral Patterns


State

狀態對象依賴注入到context對象,context對象根據它的狀態改變而改變它的相關行為(可通過調用內部的狀態對象實現相應的具體行為)

Back to Behavioral Patterns


Stategy

策略對象依賴注入到context對象,context對象根據它的策略改變而改變它的相關行為(可通過調用內部的策略對象實現相應的具體策略行為)

Back to Behavioral Patterns


Template Method

將這些通用算法抽象出來,在一個抽象類中公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但調用將以抽象類中定義的方式進行。

Back to Behavioral Patterns


Visitor

Back to Behavioral Patterns


Reference Credits

Course Link

References resources - Deviq