设计模式
- 核心目的是高内聚,低耦合,增强代码的扩展性和结构性
UML
UML关系图
demo
- 箭头上的数字代表 1 个学生可以不参加课程,也可以无限制参加各种课程
- 1 代表一个,0..* 代表 0 个到无限个
[!important] 多重性 多重性应用于关联的目标端,说明源类的每个实例与目标类实例的连接个数。 0..*:0个或多个 3..7:指定范围(3~7个,包含3和7)
依赖
- 对于两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。
- 类 A 使用到了类 B,而这种使用关系具有偶然性,临时性,非常弱的,但是 B 类中的变化会影响到类 A
[!example] 你是一名出租车司机,每天开着公司给你分配的车去载客,而每天出租车可能不同,我只是个司机,公司给我什么车我就开什么车,我使用这个车。
关联
- 对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系。双向关联的话箭头可以省略。
- 两个类中一种强依赖关系,比如我和我的朋友,这种关系比依赖更强,不存在依赖关系中的偶然性,关系也不是临时的,一般是长期性的。
[!example] 我是一名老司机,车是我自己的,我拥有这辆车,平时也会用着辆车去载客人。
聚合
- 聚合关系是关联关系的一种,耦合度强于关联,他们的代码表现是相同的,仅仅是在语义上有所区别:关联关系的对象间是相互独立的,而聚合关系的对象之间存在着包容关系,他们之间是“整体-个体”的相互关系。
- 聚合关系中作为成员变量的类一般使用 set 方法赋值
[!quote] 依赖:用完就扔。
关联,聚合:不属于我,但是用完先放着。
组合:是我的一部分,用完保存好。
组合
- 相比于聚合,组合是一种耦合度更强的关联关系。存在组合关系的类表示“整体-部分”的关联关系,“整体”负责“部分”的生命周期,他们之间是共生共死的;并且“部分”单独存在时没有任何意义。
[!example] 人和灵魂,身体之间是组合关系,当人的生命周期开始时,必须同时拥有灵魂和肉体,当人的生命周期结束时,灵魂肉体随之消亡;无论是灵魂还是肉体,都不能单独存在,他们必须作为人的组成部分存在。
继承
实现
[!tip] 参考 https://zhuanlan.zhihu.com/p/24576502
六大原则
开闭原则
- 总原则
- 对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果
单一职责原则
- 不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
里氏替换原则
- 最基础的原则,是实现的底层基础
- 父类出现的地方,子类一定可以出现.任何基类可以出现的地方,子类一定可以出现
依赖倒转原则
- 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。相当于行为准则
合成复用原则
- 原则是尽量首先使用合成/聚合的方式,而不是使用继承。
最少知道原则
- 类出现就应该遵守的基本原则
- 一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
创建型模式
这部分模式用于创建对象
单例模式
- 底层的实质是需要规定某个对象全局只能初始化一次
懒汉模式
- 存在内存泄漏问题(使用智能指针或者嵌套类)
- 多线程下多个线程可能同时拿到(加锁)
// version 1.0
class Singleton
{
private:
static Singleton* instance;
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton* getInstance() {
if(instance == NULL)
instance = new Singleton();
return instance;
}
};
// init static member
Singleton* Singleton::instance = NULL;
饿汉模式
// version 1.3
class Singleton
{
private:
static Singleton instance;
private:
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance() {
return instance;
}
}
// initialize defaultly
Singleton Singleton::instance;
Best
- 通过static的形式
// version 1.2
class Singleton
{
private:
Singleton() { };
~Singleton() { };
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance(){
static Singleton instance;
return instance;
}
};
工厂模式
简单工厂
[!question] 工厂模式问题 会遇到比如某些工厂余姚设置某些特别的属性时候不能够通用的问题,虽然可以通过static 设置但是不够优雅
- 本质是调用时候不需要使用调用类的名字,而是使用一个枚举变量就创建类,返回值是一个抽象的父类指针
- 优点是这样只需要一个枚举就能创建不同的对象,创建的函数中可以进行具体的操作,将实际的功能的类和创建的类彻底分开
enum CTYPE {COREA, COREB};
class SingleCore{
public:
virtual void Show() = 0;
};
//单核A
class SingleCoreA: public SingleCore{
public:
void Show() { cout<<"SingleCore A"<<endl; }
};
//单核B
class SingleCoreB: public SingleCore{
public:
void Show() { cout<<"SingleCore B"<<endl; }
};
//唯一的工厂,可以生产两种型号的处理器核,在内部判断
class Factory{
public:
SingleCore* CreateSingleCore(enum CTYPE ctype){
if(ctype == COREA) //工厂内部判断
return new SingleCoreA(); //生产核A
else if(ctype == COREB)
return new SingleCoreB(); //生产核B
else
return NULL;
}
};
抽象工厂和工厂方法
- 工厂方法是简单工厂的升级模式,用于在多个成系列的产品需要一起创造的时候,使用一个工厂生产==一个系列的产品==
# 程序会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初
# 始化阶段)。
class ApplicationConfigurator is
method main() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
factory = new WinFactory()
else if (config.OS == "Mac") then
factory = new MacFactory()
else
throw new Exception("错误!未知的操作系统。")
Application app = new Application(factory)
建造者模式
- 用于与解决复杂对象的构建,实质上就是分开不同的步骤,在加上一个指挥类负责调用函数构建
class Builder
{
public:
virtual void BuildHead() {}
virtual void BuildBody() {}
virtual void BuildLeftArm(){}
virtual void BuildRightArm() {}
virtual void BuildLeftLeg() {}
virtual void BuildRightLeg() {}
};
//构造瘦人
class ThinBuilder : public Builder
{
public:
void BuildHead() { cout<<"build thin body"<<endl; }
void BuildBody() { cout<<"build thin head"<<endl; }
void BuildLeftArm() { cout<<"build thin leftarm"<<endl; }
void BuildRightArm() { cout<<"build thin rightarm"<<endl; }
void BuildLeftLeg() { cout<<"build thin leftleg"<<endl; }
void BuildRightLeg() { cout<<"build thin rightleg"<<endl; }
};
//构造胖人
class FatBuilder : public Builder
{
public:
void BuildHead() { cout<<"build fat body"<<endl; }
void BuildBody() { cout<<"build fat head"<<endl; }
void BuildLeftArm() { cout<<"build fat leftarm"<<endl; }
void BuildRightArm() { cout<<"build fat rightarm"<<endl; }
void BuildLeftLeg() { cout<<"build fat leftleg"<<endl; }
void BuildRightLeg() { cout<<"build fat rightleg"<<endl; }
};
//构造的指挥官
class Director
{
private:
Builder *m_pBuilder;
public:
Director(Builder *builder) { m_pBuilder = builder; }
void Create(){
m_pBuilder->BuildHead();
m_pBuilder->BuildBody();
m_pBuilder->BuildLeftArm();
m_pBuilder->BuildRightArm();
m_pBuilder->BuildLeftLeg();
m_pBuilder->BuildRightLeg();
}
};
结构型模式
这部分模式侧重于类和类之间的组合桥梁成为更大的结构
装饰模式
- 实现和继承类似的功能,但是因为继承耦合度增加,因此用这个模式是替代,==使用组合代替继承==,组合之后再加上额外的功能在新的类中
适配器模式
- 用于构建一个类将两个本来不能兼容的类适配,方法是创建一个适配器类,继承已经存在的类和需要实现的interface,在需要实现的interface中操作
- 需要使用的时候直接用适配器类代替已经存在的类,不需要使用以前的类
func main{
client := &Client{}
mac := &Mac{}
client.InsertLightningConnectorIntoComputer(mac)
windowsMachine := &Windows{}
windowsMachineAdapter := &WindowsAdapter{
windowMachine: windowsMachine,
}
client.InsertLightningConnectorIntoComputer(windowsMachineAdapter)
}
代理模式
- 实际上就是套娃,在外层再封装一个模式作为代理层,用于间接调用(因为可能直接的部分是库无法更改)
- MCV中的m实际上就是代理层,还有限流缓存等功能
行为型模式
这部分模式侧重点在类和类之间的关系,相互调用和依赖
观察者模式
- 就是发布订阅模型,具体实现可以参考redis实现 > 订阅频道
- 普通情况下我们通常采用传递函数,然后回调的方法使用,这样的局限性在于函数不能携带状态和数据,因此观察者模式抽象出一个订阅者的interface,实现了这个interface内函数的类都可以直接传入
type Observer interface {
update(string)
}
type Customer struct {
id string
}
func (c *Customer) update(itemName string) {
fmt.Printf("Sending email to customer %s for item %s\n", c.id, itemName)
}
func main() {
shirtItem := newItem("Nike Shirt")
observerFirst := &Customer{id: "abc@gmail.com"}
observerSecond := &Customer{id: "xyz@gmail.com"}
shirtItem.register(observerFirst)
shirtItem.register(observerSecond)
shirtItem.updateAvailability()
}
策略模式
- 和工厂模式类似,但是核心区别是工厂模式生产的是不同的对象,然后通过基类对象指针接收着整个新对象,但是策略模式是先生成对象,然后通过策略模式更改这个对象的行为
- 工厂模式中只管生产实例,具体怎么使用工厂实例由调用方决定,策略模式是将生成实例的使用策略放在策略类中配置后才提供调用方使用,进一步封装了实例的使用,实际上是封装了使用的场景,工厂只封装了生产的场景
PeopleFactory peopleFactory = new PeopleFactory();
People people = peopleFactory.getPeople("Xiaohong"); System.out.print("工厂模式-------------"); people.run(); StrategySign strategySign = new StrategySign("Xiaohong"); System.out.print("策略模式-------------");strategySign.run();
[!tip] 参考 https://cloud.tencent.com/developer/article/1873154
迭代器模式
- 就是C++的迭代器模式: 声明所有迭代器的父类interface,特定的对象实现一个特定的迭代器,然后将这个特定的迭代器作为自己的成员暴露出去,外部调用的是第五代其interface的实现方法
type Iterator interface {
hasNext() bool
getNext() *User
}
中介者模式
- 大家都依赖一个中心化的类,避免相互依赖提高耦合度,这个平时也经常用
重构
重构时机
[!important] 需求的变化导致重构,如果一段代码能正常工作,并且不会再被修改,那么完全可以不去重构它
- ==三次法则==:如果三次做类似的事情(写相似的代码),那么就应该对代码进行重构
[!important] 重构前,保证有一套可靠的测试集,这些测试必须具有自我检验的能力,测试参考测试系统
代码坏味道
过长函数
- 保证代码的圈复杂度尽量在10以内,最多不能超过20
- 将逻辑提取到小函数进行封装
- 共同的逻辑也提取到小函数封装
- 如果是class中的共同代码考虑提取到父类中进行
过长参数列表
- 查询代替参数,如果参数可以通过其他参数计算得到,那么封装成为一个独立的函数使用查询获得
- 引用参数对象,合并参数为对象直接传递对象
- 去除参数标记,将标记拆分成为多个函数,使用函数名区分开,多个函数中公共部分拆分提取公共函数
参考
- https://zhuanlan.zhihu.com/p/37469260
- https://zhuanlan.zhihu.com/p/431714886
- https://refactoringguru.cn/design-patterns/cpp