面对对象

2022/07/22

关于C++面对对象与设计模式的一些笔记

1. 面对对象的四大特性

面对对象的四个基本特性为 encapsulation, abstraction, inheritance, and polymorphism,是学习面对对象的第一步。

1.1 封装(encapsulation)

In object-oriented programming (OOP), encapsulation refers to the bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object’s components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods.

封装意思是将类中的某些变量或方法设置为privateprotected, 不允许用户直接访问这些变量或方法,只能通过类定义的方法来访问。

Sclass Demo{
    private:
        int value = 0;
    public:
        void set(int n){
            value = n;
        }
        int get(){
            return value;
        }
};

1.2 抽象 (Abstraction)

The process of removing physical, spatial, or temporal details or attributes in the study of objects or systems to focus attention on details of greater importance; it is similar in nature to the process of generalization; the creation of abstract concept-objects by mirroring common features or attributes of various non-abstract objects or systems of study – the result of the process of abstraction.

抽象重点在于把实现的过程隐藏起来只暴漏出接口。抽象与封装的区别在于封装重点在于数据隐藏,而抽象在于功能的抽象。

#include <iostream>
using namespace std;

class Summation {
private:
    // private variables
    int a, b, c;
public:
    void sum(int x, int y){
        a = x;
        b = y;
        c = a + b;
        cout<<"Sum of the two number is : "<<c<<endl;
    }
};
int main(){
    Summation s;
    s.sum(5, 4);
    return 0;
}

1.3 继承 (Inheritance)

In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation. Also defined as deriving new classes (sub classes) from existing ones such as super class or base class and then forming them into a hierarchy of classes

继承是指如果有一些类具有一些相同的特征,就可以把这些特征抽象出来定义一个基类,然后其他的类继承这个基类,增强代码复用。

class Duck{
    // some commod property
public:
    virtual void fly(){
        std::cout << "Look I'm flying" << std::endl;
    }
};

class Mallard :public Duck{

};

class WoodDuck :public Duck{
public:
    void fly(){     // override fly method
        std::cout << "I can not flying" << std::endl;
    }
};
int main(){
    Mallard mallard = Mallard();
    WoodDuck woodDuck = WoodDuck();
    mallard.fly();
    woodDuck.fly();
}

1.4 多态 (polymorphism)

In programming language theory and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. The concept is borrowed from a principle in biology where an organism or species can have many different forms or stages.

多态是从生物学中借鉴过来的一个词语,意思是是给一个结构提供不同的类型,或者说是使用同一个符号的表示不同的类型。在C++中可以通过虚函数定义基类,然后再用多个子类对虚函数重写,这样就可以使用基类的指针动态表示多个子类。

class Duck{
    // some commod property
public:
    virtual void fly(){
        std::cout << "Look I'm flying" << std::endl;
    }
};

class Mallard :public Duck{

};

class WoodDuck :public Duck{
public:
    void fly(){     // override fly method
        std::cout << "I can not flying" << std::endl;
    }
};

void flyfly(Duck& duck){
    duck.fly();
}

int main(){
    Mallard mallard = Mallard();
    WoodDuck woodDuck = WoodDuck();
    fly(mallard);
    fly(woodDuck);
}

这里的flyfly函数接收一个Duck类型的引用参数,然后调用fly函数。可以分别向这个函数传入mallardwoodDuck两个不同的类,因为基类中fly函数为虚函数所以在flyfly函数中因为传入的类不同所展现出来的行为也不同。

1.4.1 动态绑定 (Dynamic binding)

动态绑定,也称为隐式类型转换,意思是基类与其派生类都可以通过基类这个数据类型来表示。

1.4.2 虚函数 (Virtual function)

对于基类来说应该要区分那些需要派生类来重写的和不需要重写的函数,在需要重写的函数前面加上一个virtual关键字,表示这个函数为虚函数。在通过引用或者指针来调用虚函数的时候,可以展现出上面所描述的动态绑定的性质,如果通过引用或指针所调用的函数不是虚函数则等于调用基类对应的函数。

1.43. 纯虚函数 (Pure virtual function)

纯虚函数就是在虚函数的基础上再参数括号后面加上一个=0并且去除函数体,就像这样

class Duck{
    // some commod property
public:
    virtual void fly() = 0;
};

一个类只要有一个纯虚函数则称为抽象类(abstract class), 抽象类不能进行实例化也不能作为函数的参数,继承抽象类的派生类必须重写基类的所以抽象类,如果有一个没写则这个派生类也为抽象类。

2. 面对对象的SOLID原则

SOLID是: Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle首字母的缩写,这些原则保证通过面对对象写出来的代码具有良好的可读性,可扩展性和可维护性

2.1 单一职责原则 (Single Responsibility Principle)

The idea behind the SRP is that every class, module, or function in a program should have one responsibility/purpose in a program. As a commonly used definition, “every class should have only one reason to change”.

单一职责原则的意思一个类就只负责一个功能,当一个类需要更改的时候只与它所负责的那个功能有关

2.2 开放封闭原则 (Open-Closed Principle)

The open-closed principle states that software entities should be open for extension, but closed for modification.

开放封闭原则就是需要添加一个新的功能允许添加代码但是不允许修改原有代码

2.3 里氏代换原则 (Liskov Substitution Principle)

The Liskov substitution principle simply implies that when an instance of a class is passed/extended to another class, the inheriting class should have a use case for all the properties and behavior of the inherited class.

里氏代换原则是指如果使用的是一个基类,那么将这个基类换成它的派生类行为应该不会发生变化

2.4 结构隔离原则 (Interface Segregation Principle)

The interface segregation principle states that the interface of a program should be split in a way that the user/client would only have access to the necessary methods related to their needs.

结构隔离原则是指每个接口中不存在派生类用不到却必须实现的方法,如果不然,就要将接口拆分,使用多个隔离的接口。

2.5 依赖倒转原则 (Dependency Inversion Principle)

High-level modules should not import anything from low-level modules. Both should depend on abstractions (e.g., interfaces).

Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

依赖倒转原则是指抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要对实现编程。

3.6 UML

UML 是 Unified Modeling Language 的缩写,中文名为统一建模语言,它包含很多种类型,比如类图,时序图,状态图,活动图,部署图,等等。在设计模式中类与类之间的关系一般使用类图来表示。

3.6.1 类图

再 UML 是用类图来表示一个类,可以表示出一个类的成员变量与成员函数。

在 UML 中用一个三层矩形来表示一个类,第一层表示类名,第二层表示类的成员变量,第三次表示类的成员函数。在上面的例子中可以看到有些类的前面都有一个 + 或者 -,这表示的是可见性,其中 + 表示为 public, - 表示为 private,# 表示为 Protected, ~ 表示为 Package/Interval。

3.6.2 类之间的关系

这些是不同类之间的关系,一共有八种,可以查看这个例子来解读

_images/uml_class_struct.jpg

3. 设计模式

一些简单的设计模式及其C++代码实现,这部分参考了《Design Pattern Elements of Reusable Object-Oriented Softw》,Refactoring guru 以及 Graphic Design Patterns

设计模式

3.1 创造型模式(Creational Pattern)

创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。

创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。

3.1.1 简单工厂模式(Simple Factory)

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

SimpleFactory.jpg

3.1.2 工厂方法模式(Factory Method)

工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

FactoryMethod.jpg

以下面这个披萨生产模型为例,披萨作为抽象抽象产品,纽约口味的奶酪披萨作为具体产品,披萨店作为抽象工厂,纽约披萨店作为具体工厂,实现createPizza函数,根据传入参数将orderPizza函数中的披萨参数实例化为具体产品并返回那个具体产品。

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

using namespace std;

class Pizza{
protected:
    string name;
    string dough;
    string sauce;
public:
    void prepare(){
        cout << "Preparing " + name << endl;
        cout << "Tossing dought..." << endl;
        cout << "Adding sauce..." << endl;
    }
    void back(){
        cout << "Bake for 25 minutes" << endl;
    }
    void cut(){
        cout << "Cutting the pizza into diagonal slices" << endl;
    }
    void box(){
        cout << "Place pizza in official PizzStore box" << endl;
    }
    string getName(){
        return name;
    }
};

class NYStyleCheesePizza: public Pizza{
public:
    NYStyleCheesePizza(){
        name = "NY Style Sauce and Cheese Pizza"; 
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
    }
};

class PizzaStore{
public:
    virtual Pizza* createPizza(string type) = 0;

    Pizza* orderPizza(string type){
        Pizza* pizza = createPizza(type);

        pizza->prepare();
        pizza->back();
        pizza->cut();
        pizza->box();

        return pizza;
    }
};

class NYPizzaStore : public PizzaStore{
public:
    Pizza *createPizza(string type) override {
        if(type == "cheese"){
            return new NYStyleCheesePizza();
        }
        return nullptr;
    }
};

int main(){
    PizzaStore *nyStore = new NYPizzaStore();

    Pizza* pizza = nyStore->orderPizza("cheese");

    return 0;
}

3.1.3 抽象工厂模式(Abstract Factory)

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。

AbatractFactory.jpg

3.1.4 单例模式(Singleton)

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

Singleton.jpg
  1. 懒汉模式

    直到第一次用到类的实例时才去实例化

    #include <mutex>
    
    using namespace std;
    
    class Singleton {
    private:
     static mutex mtx;
     static Singleton* instance;
     Singleton() {
    
     }
     Singleton(const Singleton& temp) {}
     Singleton& operator = (const Singleton& temp) {}
    public:
     static Singleton* getInstance() {
         lock_guard<mutex> guard(mtx);
         if (instance == nullptr) {
             instance = new Singleton();
         }
         return instance;
     }
    };
    Singleton* Singleton::instance = nullptr;
    
    int main() {
    
     return 0;
    }
  2. 饿汉模式

    类定义的时候就实例化。

    class Singleton{
    private:
        static Singleton* instance;
        Singleton(const Singleton& temp){}
        Singleton& operator=(const Singleton& temp){}
    protected:
      Singleton(){} 
    public:
        static Singleton* getInstance(){ 
            return instance;    
        }
    };
    Singleton* Singleton::instance = new Singleton();

3.1.4 建造者模式(Builder)

造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式。

Builder.jpg

3.1.5 源型模式(Prototype)

原型模式是通过实例化的原型来指定需要创建的对象,并且通过复制原型的创建新的对象

img

3.2 结构型模式

结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。

结构型模式可以分为类结构型模式和对象结构型模式:

3.2.1 适配器模式(Adapter)

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

对象适配器:

Adapter.jpg

类适配器:

Adapter_classModel.jpg

3.2.2 桥接模式(Bridge)

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

Bridge.jpg

3.2.3 装饰模式(Decorator)

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

Decorator.jpg

3.2.4 外观模式(Facade)

外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。

Facade.jpg

3.2.5 享元模式(Flyweight)

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

Flyweight.jpg

3.2.6 代理模式(Proxy)

代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。

Proxy.jpg

3.3 行为型模式

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。

行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。

通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

行为型模式分为类行为型模式和对象行为型模式两种:

3.3.1 职责链模式(Chain of Responsibility)

Structure of the Chain of Responsibility example

3.3.2 命令模式(Command)

命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。在这个模式中命令的发送者与命令的执行者分开了,执行者只需要执行命令,并不知道所执行的命令是什么

Command.jpg

这里 Invoker 只接受一个 Command 接口,不需要知道具体的命令是什么,也不需要知道命令的接受者是谁。Receiver 则是吧自己塞入 Command 中,每一个 Receiver 都实现具体的 action 表示命令调用后的行为,但是他并不关心是谁调用。也就说这里是通过 CommandInvokerReceiver 分离开了。

3.3.3 解释器模式(Interpreter)

3.3.4 迭代器模式(Iterator)

3.3.5 中介者模式(Mediator)

中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。

Mediator.jpg

首先可以看一下如果不使用中介者模式两个类之间的交互的代码

class Base{
protected:
    int msgValue{};
public:
    virtual void sendMsg(Base*b, int msg) = 0;
    virtual void receiveMsg(int msg) = 0;
};

class A: public Base{
public:
    void sendMsg(Base* b, int msg) override{
        b->receiveMsg(msg);
    }
    void receiveMsg(int msg) override{
        msgValue = msg;
    }
};

class B: public Base{
public:
    void sendMsg(Base* b, int msg) override{
        b->receiveMsg(msg);
    }
    void receiveMsg(int msg) override{
        msgValue = msg;
    }
};

int main(){
    A* a = new A();
    B* b = new B();
    a->sendMsg(b, 3);
    b->sendMsg(a, 4);
}

这样程序可以运行但是也存在者一些问题

  1. 在用户与用户直接聊天的设计方案中,用户对象之间存在很强的关联性,将导致系统出现如下问题:
  2. 系统结构复杂:对象之间存在大量的相互关联和调用,若有一个对象发生变化,则需要跟踪和该对象关联的其他所有对象,并进行适当处理。
  3. 对象可重用性差:由于一个对象和其他对象具有很强的关联,若没有其他对象的支持,一个对象很难被另一个系统或模块重用,这些对象表现出来更像一个不可分割的整体,职责较为混乱。
  4. 系统扩展性低:增加一个新的对象需要在原有相关对象上增加引用,增加新的引用关系也需要调整原有对象,系统耦合度很高,对象操作很不灵活,扩展性差。
  5. 在面向对象的软件设计与开发过程中,根据“单一职责原则”,我们应该尽量将对象细化,使其只负责或呈现单一的职责。
  6. 对于一个模块,可能由很多对象构成,而且这些对象之间可能存在相互的引用,为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式,这就是中介者模式的模式动机。

3.3.6备忘录模式(Memento)

3.3.7观察者模式(Observer)

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

Obeserver.jpg

3.3.8 状态模式(State)

状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

State.jpg

3.3.9 策略模式(Strategy)

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

Strategy.jpg

3.3.10 模板方法模式(Template Method)

3.3.11 访问者模式(Visitor)