Object-oriented Programming Design Pattern (C++)

Templates for efficient programming.

Principles

  1. Open close principle. Extending instead of modifying when programm needs more features
  2. Liskov substitution principle. Use derived classes
  3. Dependence inversion principle. Program interfaces
  4. Interface segregation principle. Use several separate interfaces
  5. Demeter principle. Keep modules isolated
  6. Composite reuse principle. Use composition instead of inheritance

Creational Patterns

Used when creating object instance.

Factory Pattern

Create different derived classes simply.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Shape {
public:
virtual void draw() = 0;
}

class Square: public Shape {
public:
virtual void draw() override {

}
}

class ShapeFactory {
public:
// use smart_pointer to manage the deallocation automatically
static std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "Square") {
return std::make_unique<Square>();
} else if (...) {
//
} else {
return nullptr;
}
}
}

Abstract Factor Pattern

A factory produces other factories which provide real functions.

Singleton Pattern

1 class has 1 instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Singleton {
public:
static Singleton& getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
static Singleton instance;
return instance;
}

void doSomething() {

}

Singleton(Singleton const&) = delete;
void operator=(Single const&) = delete;

private:
Singleton() {

}
static std::mutex mutex_;
}

std::mutex Singleton::mutex_;

Builder Pattern

Construct complex objects step-by-step, and provides the ability to create variations of an object using the same construction process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Pizza {
public:
void setDough(const std::string& dough) {

}

void setSauce(const std::string& source) {

}
}

class PizzaBuilder {
public:
virtual void buildDough() = 0;
virtual void buildSauce() = 0;
virtual Pizza* getPizza() = 0;
}

class SytleAPizzaBuilder: public PizzaBuilder {
public:
virtual void buildDough() override {

}

virtual void buildSauce() override {

}

Pizza* getPizza() override {
return pizza_;
}

SytleAPizzaBuilder() {
pizza_ = new Pizza();
}
private:
Pizza* pizza_
}

Prototype Pattern

Class supports clone function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Prototype {
public:
virtual ~Prototype() {

}

virtual std::unique_ptr<Prototype> clone() = 0;
virtual void doSomething() = 0;
}

class ConcretePrototype: public Prototype {
public:
ConcretePrototype() {

}

ConcreteProtoype(const ConcreteProtoype& obj) {

}

virtual std::unique_ptr<Prototype> clone() override {
// calls the copy constructor
return std::make_unique<ConcretePrototype>(*this);
}
}

Structural Patterns

Used when using several object instances.

Adapter Pattern

Accept an object instance, then wraps its interfaces to be new interfaces.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Adaptee interface that needs to be adapted to the Target interface
class BaseMediaPlayer {
public:
virtual void playMp4(std::string filename) = 0;
virtual void playAvi(std::string filename) = 0;
};

// Concrete implementation of the Adaptee interface
class Mp4Player : public BaseMediaPlayer {
public:
void playMp4(std::string filename) override {
std::cout << "Playing MP4 file: " << filename << std::endl;
}
void playAvi(std::string filename) override {
// Do nothing
}
};

// Target interface that the client code expects to use
class ClientMediaPlayer {
public:
virtual void play(std::string filename) = 0;
};

// Adapter that adapts the Adaptee interface to the Target interface
class MediaAdapter : public ClientMediaPlayer {
public:
MediaAdapter(BaseMediaPlayer* player) : player_(player) {}
void play(std::string filename) override {
if (filename.find(".mp4") != std::string::npos) {
player_->playMp4(filename);
} else if (filename.find(".avi") != std::string::npos) {
player_->playAvi(filename);
} else {
std::cout << "Unsupported file format" << std::endl;
}
}
private:
BaseMediaPlayer* player_;
};

// Concrete implementation of the Target interface that uses the Adapter
class AudioPlayer : public ClientMediaPlayer {
public:
void play(std::string filename) override {
// Check if the file format is supported
if (filename.find(".mp3") != std::string::npos) {
std::cout << "Playing MP3 file: " << filename << std::endl;
} else if (filename.find(".vlc") != std::string::npos || filename.find(".mp4") != std::string::npos) {
// Use the MediaAdapter to play VLC and MP4 files
MediaAdapter adapter(new Mp4Player());
adapter.play(filename);
} else {
std::cout << "Unsupported file format" << std::endl;
}
}
};

Bridge Pattern

Separate abstraction and implementation. Decouple classes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Render {
public:
virtual void renderCircle() = 0;
};

class VectorRenderer: public Render {
public:
virtual void renderCircle() override {

}
};

// class Shape is abstraction, while the Renderer obj is implementation. Use a reference so that the real implementation of Renderer could change.
class Shape {
protected:
// can be also pointer, but use reference to avoid null pointer issue
Renderer& renderer;
public:
Shape(Renderer& renderer): renderer(renderer) {

}
virtual void draw() = 0;
}

class Circle: public Shape {
public:
Circle(Renderer& renderer): Shape(renderer) {

}

virtual void draw() override {
renderer.renderCircle();
}
}

Filter/Criteria Pattern

Filter a list of objects by some criteria.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person {

};

class Criteria {
public:
virtual std::vector<Person> meetCriteria(const std::vector<Person>& persons) = 0;
};

class AgeCriteria: public Criteria {
public:
AgeCriteria(int age) {}

virtual std::vector<Person> meetCriteria(const std::vector<Person>& persons) override {
// filter persons by age
}
};

class AddCriteria: public Criteria {
public:
AddCriteria(Criteria* criteria1, Criteria* criteria2) {}

virtual std::vector<Person> meetCriteria(const std::vector<Person>& persons) override {
// filter persons by criteria1 and criteria2
}
private:
Criteria* criteria1;
Criteria* criteria2;
};

Composite Pattern

Maintain objects in tree structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Component {
public:
virtual void operation() = 0;
};

class Leaf: public Component {
public:
virtual void operation() override {

}
};

class Composite: public Component {
public:
void add(std::shared_ptr<Component> component) {

}

virtual void operation() override {
for(auto& component: components) {
component->operation();
}
}

private:
std::vector<std::shared_ptr<Component>> components;
}

Decorator Pattern

A wrapper class containing the original class and implement more behaviros.

Comparing with adapter pattern which changes the interface, the wrapper class of decorator pattern has the same interface of the original class.

Facade Pattern

Maintains several component object together in single class, and this class is responsible to execute them.

Flyweight Pattern

Avoid to create object instances repeatly. Use a map inside class to store previous created instances.

Proxy Pattern

Create a new class to present the original class to avoid extra efforts from clients.

Comparing with decorator pattern which focues on adding new behaviros, the proxy pattern focues on controlling access to an object.

Behavioral Patterns

Used when the class performs behaviors.

Chain of Responsibility Pattern

Chain a series of handlers. When a request comes in, it will be handled by handler one by one.

Command Pattern

Wraps request into an object and pass into class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Command {
public:
virtual void execute() = 0;
}

class LightOnCommand: public Command {
public:
LightOnCommand() {}

virtual void execute() override {

}
}

class LightOffCommand: public Command {
public:
LightOffCommand() {}

virtual void execute() override {

}
};

class RemoteControl {
public:
void setCommand(std::shared_ptr<Command> command) {

}

void pressButton() {
m_command -> execute();
}

private:
std::share_ptr<Command> m_command;
};

Interpreter Pattern

Provide an interpret interface which evaluate sentences in a language.

Iterator Pattern

Provide a function to traverse container’s items without exposing the implementation of container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename T>
class Iterator {
public:
virtual bool hasNext() = 0;
virtual T next() = 0;
}

template <typename T>
class VectorIterator: public Iterator<T> {
public:
VectorIterator(const std::vector<T>& vec) {}
virtual bool hasNext() override {
return m_index < m_vec.size();
}
virtual T next() override {
return m_vec[m_index++];
}

private:
std::vector<T> m_vec;
int m_index;
}

Mediator Pattern

Decouple different objects. Serve in the middle to handle communications between objects.

Memento Pattern

Store the object’s internal state and recover the object later.

Observer Pattern

Subscribers register (attach) in the publisher and receive notice.

State Pattern

Behaviors of class are controlled by its state. State machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// forward declaration
class Context;

class State {
public:
virtual void handle(std::shared_ptr<Context> context) = 0;
};

class StateA: public State {
public:
virtual void handle(std::shared_ptr<Context> context) override {

}
};

class StateB: public State {
public:
virtual void handle(std::shared_ptr<Context> context) override {

}
};

class Context {
public:
void setState(std::shared_ptr<State> state) {

}

void request() {
state_ -> handle(*this);
}
}

Null Object Pattern

Instead of using nullptr in some cases, use a special class which represents the nullptr, i.e. has the same interfaces but perform nothing.

Strategy Pattern

Dynamically change the behavior of an object at runtime by providing multiple strategies to choose from.

Comparing with state pattern which is used to change an object’s behavior based on its internal state, strategy pattern is used to change an object’s behavior by selecting one of several interchangeable algorithms.

Template Pattern

Abstract class defines how to perform the executions, while derived classes implement the real executions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Game {
public:
virtual void start() = 0;
virtual void play() = 0;
virtual void end() = 0;

void run() {
start();
play();
end();
}
};

class TicTacToe: public Game {
public:
virtual void start() override {

}

virtual void play() override {

}

virtual void end() override {

}
};

Visitor Pattern

Use different visitors to change the behaviors of a class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Foo;
class Bar;

class Visitor {
public:
virtual void visit(Foo& foo) = 0;
virtual void visit(Bar& bar) = 0;
};

class DoSomethingVisitor: public Visitor {
public:
virtual void visit(Foo& foo) override {

}

virtual void visit(Bar& foo) override {

}
};

class Visitable {
public:
virtual void accept(Visitor& visitor) = 0;
};

class Foo {
public:
virtual void accept(Visitor& visitor) override {
visitor.visit(*this);
}
}

Reference