Design patterns

Design patterns 是程式設計中常見問題的典型解決套路,適當使用可以加強程式可擴展性和可延續性。可分為三類:

Creational

Creational design patterns 泛指在創造物件上的方法

Singleton

Singleton 的概念是讓一個 Class 只能被 Instantiate 一次。這可以用在不可以有兩個 instance 的物件上(例如機器底盤不可能會有兩個)。

Singleton in c++:

#include <iostream>
using namespace std;

class Chassis {
    private:
        // pointer storing the only instance
        static Chassis* instance; 
        
        // new constructor
        Chassis() {}
        
    public:
        // delete default constructor
        Chassis(const Chassis& obj) = delete;
        
        // get the only instance
        static Chassis* get_instance() {
            if (instance == nullptr) {
                instance = new Chassis();
            }
            return instance;
        }
};

Chassis* Chassis::instance = NULL;

int main() {
    Chassis* c = Chassis::get_instance();
    Chassis* d = Chassis::get_instance();

    // addresses for the first and second instance are equal
    std::cout << c << std::endl;
    std::cout << d << std::endl; 
    return 0;
}

Factory

Factory 的概念是透過一個 Class 進行生成。這個 Class 就像一個工廠一樣,基於輸入創造不同種類的物件

Factory in c++:

#include <iostream>
using namespace std;


class Robot {
};

class TwoMotorRobot: public Robot {

    public:
        TwoMotorRobot() {
            std::cout << "Two motor robot" << std::endl;
        }
};

class FourMotorRobot: public Robot {
    public:
        FourMotorRobot() {
            std::cout << "Four motor robot" << std::endl;
        }
};

class RobotFactory {
    public:
        static Robot* createRobot(int type) {
            if (type == 1) {
                return new TwoMotorRobot();
            } 
            else {
                return new FourMotorRobot();
            }
        }
};

int main() {
    RobotFactory::createRobot(2);
    return 0;
}

Builder

Builder 顧名思義,就像建築師一樣一步一步的蓋房子,是創造複雜物件的其中一種方式

#include <iostream>
using namespace std;

class Robot {
    private:
        std::string chassis;
        std::string arm;
    public:
        Robot(std::string chassis, std::string arm) {
            this->chassis = chassis;
            this->arm = arm;
        }
        void print_info() {
            std::cout << this->chassis + " " +  this->arm << std::endl;
        }
};

class RobotBuilder {
    private:
        std::string chassis;
        std::string arm;
    public:
        RobotBuilder set_chassis(std::string chassis) {
            this->chassis = chassis;
            return *this;
        }
        RobotBuilder set_arm(std::string arm) {
            this->arm = arm;
            return *this;
        }
        Robot* build() {
            return new Robot(this->chassis, this->arm);
        }
};

int main() {
    RobotBuilder builder = RobotBuilder();
    Robot* r = builder.set_chassis("chassis").set_arm("arm").build();
    r->print_info();
    return 0;
}

Structural

Structural design patterns 泛指物件互相使用和架構的方式

Facade

Facade 的概念是用一個 Super class 創造一個整潔的使用者介面。使用這個物件的人不需要知道其內容並單一操控,只需要使用 Super class 的大功能即可

class House {
    private:
        Lights ligths;
        AirConditioner air_conditioner;
        SmartWindow window;
        WaterSystem water_system;
    public:
        House();
        void startup() {
            lights.turn_on();
            water_system.turn_on();
        }
        void cooldown() {
            window.close();
            air_conditioner.set_temp(26);
            air_conditioner.turn_on();
        }
        void terminate() {
            lights.turn_off();
            air_conditioner.turn_off();
            water_system.turn_off();
        }
};

Composite

Composite 的概念是把物件包含的子物件組合成樹狀結構並加以處理那些子物件

// Example from https://refactoring.guru/design-patterns/composite/cpp/example

#include <algorithm>
#include <iostream>
#include <list>
#include <string>
/**
 * The base Component class declares common operations for both simple and
 * complex objects of a composition.
 */
class Component
{
	/**
	 * @var Component
	 */
protected:
	Component *parent_;
	/**
	 * Optionally, the base Component can declare an interface for setting and
	 * accessing a parent of the component in a tree structure. It can also
	 * provide some default implementation for these methods.
	 */
public:
	virtual ~Component() {}
	void SetParent(Component *parent)
	{
		this->parent_ = parent;
	}
	Component *GetParent() const
	{
		return this->parent_;
	}
	/**
	 * In some cases, it would be beneficial to define the child-management
	 * operations right in the base Component class. This way, you won't need to
	 * expose any concrete component classes to the client code, even during the
	 * object tree assembly. The downside is that these methods will be empty for
	 * the leaf-level components.
	 */
	virtual void Add(Component *component) {}
	virtual void Remove(Component *component) {}
	/**
	 * You can provide a method that lets the client code figure out whether a
	 * component can bear children.
	 */
	virtual bool IsComposite() const
	{
		return false;
	}
	/**
	 * The base Component may implement some default behavior or leave it to
	 * concrete classes (by declaring the method containing the behavior as
	 * "abstract").
	 */
	virtual std::string Operation() const = 0;
};
/**
 * The Leaf class represents the end objects of a composition. A leaf can't have
 * any children.
 *
 * Usually, it's the Leaf objects that do the actual work, whereas Composite
 * objects only delegate to their sub-components.
 */
class Leaf : public Component
{
public:
	std::string Operation() const override
	{
		return "Leaf";
	}
};
/**
 * The Composite class represents the complex components that may have children.
 * Usually, the Composite objects delegate the actual work to their children and
 * then "sum-up" the result.
 */
class Composite : public Component
{
	/**
	 * @var \SplObjectStorage
	 */
protected:
	std::list<Component *> children_;

public:
	/**
	 * A composite object can add or remove other components (both simple or
	 * complex) to or from its child list.
	 */
	void Add(Component *component) override
	{
		this->children_.push_back(component);
		component->SetParent(this);
	}
	/**
	 * Have in mind that this method removes the pointer to the list but doesn't
	 * frees the
	 *     memory, you should do it manually or better use smart pointers.
	 */
	void Remove(Component *component) override
	{
		children_.remove(component);
		component->SetParent(nullptr);
	}
	bool IsComposite() const override
	{
		return true;
	}
	/**
	 * The Composite executes its primary logic in a particular way. It traverses
	 * recursively through all its children, collecting and summing their results.
	 * Since the composite's children pass these calls to their children and so
	 * forth, the whole object tree is traversed as a result.
	 */
	std::string Operation() const override
	{
		std::string result;
		for (const Component *c : children_)
		{
			if (c == children_.back())
			{
				result += c->Operation();
			}
			else
			{
				result += c->Operation() + "+";
			}
		}
		return "Branch(" + result + ")";
	}
};
/**
 * The client code works with all of the components via the base interface.
 */
void ClientCode(Component *component)
{
	// ...
	std::cout << "RESULT: " << component->Operation();
	// ...
}

/**
 * Thanks to the fact that the child-management operations are declared in the
 * base Component class, the client code can work with any component, simple or
 * complex, without depending on their concrete classes.
 */
void ClientCode2(Component *component1, Component *component2)
{
	// ...
	if (component1->IsComposite())
	{
		component1->Add(component2);
	}
	std::cout << "RESULT: " << component1->Operation();
	// ...
}

/**
 * This way the client code can support the simple leaf components...
 */

int main()
{
	Component *simple = new Leaf;
	std::cout << "Client: I've got a simple component:\n";
	ClientCode(simple);
	std::cout << "\n\n";
	/**
	 * ...as well as the complex composites.
	 */

	Component *tree = new Composite;
	Component *branch1 = new Composite;

	Component *leaf_1 = new Leaf;
	Component *leaf_2 = new Leaf;
	Component *leaf_3 = new Leaf;
	branch1->Add(leaf_1);
	branch1->Add(leaf_2);
	Component *branch2 = new Composite;
	branch2->Add(leaf_3);
	tree->Add(branch1);
	tree->Add(branch2);
	std::cout << "Client: Now I've got a composite tree:\n";
	ClientCode(tree);
	std::cout << "\n\n";

	std::cout << "Client: I don't need to check the components classes even when managing the tree:\n";
	ClientCode2(tree, simple);
	std::cout << "\n";

	delete simple;
	delete tree;
	delete branch1;
	delete branch2;
	delete leaf_1;
	delete leaf_2;
	delete leaf_3;

	return 0;
}

Behavioral

Structural design patterns 泛指物件互相溝通的特殊方式

Iterator

Iterator 可讓您在不暴露其底層的情況下遍歷 array 的元件

// Example from https://refactoring.guru/design-patterns/iterator/cpp/example

#include <iostream>
#include <string>
#include <vector>

/**
 * C++ has its own implementation of iterator that works with a different
 * generics containers defined by the standard library.
 */

template <typename T, typename U>
class Iterator
{
public:
	typedef typename std::vector<T>::iterator iter_type;
	Iterator(U *p_data, bool reverse = false) : m_p_data_(p_data)
	{
		m_it_ = m_p_data_->m_data_.begin();
	}

	void First()
	{
		m_it_ = m_p_data_->m_data_.begin();
	}

	void Next()
	{
		m_it_++;
	}

	bool IsDone()
	{
		return (m_it_ == m_p_data_->m_data_.end());
	}

	iter_type Current()
	{
		return m_it_;
	}

private:
	U *m_p_data_;
	iter_type m_it_;
};

/**
 * Generic Collections/Containers provides one or several methods for retrieving
 * fresh iterator instances, compatible with the collection class.
 */

template <class T>
class Container
{
	friend class Iterator<T, Container>;

public:
	void Add(T a)
	{
		m_data_.push_back(a);
	}

	Iterator<T, Container> *CreateIterator()
	{
		return new Iterator<T, Container>(this);
	}

private:
	std::vector<T> m_data_;
};

class Data
{
public:
	Data(int a = 0) : m_data_(a) {}

	void set_data(int a)
	{
		m_data_ = a;
	}

	int data()
	{
		return m_data_;
	}

private:
	int m_data_;
};

/**
 * The client code may or may not know about the Concrete Iterator or Collection
 * classes, for this implementation the container is generic so you can used
 * with an int or with a custom class.
 */
void ClientCode()
{
	std::cout << "________________Iterator with int______________________________________" << std::endl;
	Container<int> cont;

	for (int i = 0; i < 10; i++)
	{
		cont.Add(i);
	}

	Iterator<int, Container<int>> *it = cont.CreateIterator();
	for (it->First(); !it->IsDone(); it->Next())
	{
		std::cout << *it->Current() << std::endl;
	}

	Container<Data> cont2;
	Data a(100), b(1000), c(10000);
	cont2.Add(a);
	cont2.Add(b);
	cont2.Add(c);

	std::cout << "________________Iterator with custom Class______________________________" << std::endl;
	Iterator<Data, Container<Data>> *it2 = cont2.CreateIterator();
	for (it2->First(); !it2->IsDone(); it2->Next())
	{
		std::cout << it2->Current()->data() << std::endl;
	}
	delete it;
	delete it2;
}

int main()
{
	ClientCode();
	return 0;
}

更多 design patterns: https://refactoring.guru/design-patterns/catalog

Last updated