设计模式 - 读书笔记
程序设计七大原则:
1.单一职责原则
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则。
2.开放封闭原则
一、开放封闭原则是面向对象所有原则的核心
二、1.对功能扩展开放 2.面向修改代码封闭
三、需求改变时,在不改变软件实体源代码(类、接口、方法等)的前提下,通过扩展功能,使其满足新的需求
总的来说:
一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
3.依赖倒置原则
1.高层模块不应该依赖于底层模块,两个都依赖于抽象类。
高层模块指的是调用者,底层模块就是指被调用者,或者框架。
2.抽象不应该依赖细节,细节应该依赖于抽象。我们要面向接口编程,而不是面向实现编程
3.依赖倒置原则的本质就是通过抽象 使各个类或者模块实现彼此独立,互不影响,实现模块之间的松耦合
总的来说:
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
这个我感觉比较类似COM组件的开发,把一个抽象的类当成一个成体,对外暴露的是接口,而不是释放一个具体的对象给对方。
4.里氏替换原则:
1.子类可以替换父类
2.所有引用父类对象的地方,都可以使用其子类型代替
3.如果S是T的子类型,则T类型的对象可以替换为S类型的对象
总的来说:
所有引用基类(父类)的地方必须能透明地使用其子类的对象。在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
比如所有可以用QObject的地方,我都可以用QObject的子类去代替,不然继承就是有问题的。
5.接口分离原则:
1.客户端不应该依赖它不需要的接口
2.一个类对另一个类的依赖应该建立在最小接口上。
3.接口尽量细分,不要再一个接口中放很多方法。
总的来说:
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
6.迪米特原则: 设计类的原则
1.它要求一个对象应该对其他对象有最少的了解(最好只用一些接口或者方法去操作,让类本身去对状态等属性进行保存)
2.降低类之间的耦合
3.这是一个对类创建相关的原则和法则
总的来说:
一个软件实体应当尽可能少地与其他实体发生相互作用。
应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
也就是说尽量引入一个观察者模式,做SignalCenter
在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
7.合成复用原则:
1.尽量使用对象组合,而不是继承来达到复用。
可能会有些父类的方法,新生成的子类可能用不到,这个时候就有可能会导致一些问题
继承会出现三个问题:
1.破坏了系统的封装性,基类发生改变,子类的实现也可能会发生改变
2.子类如果不需要某些方法,则系统的耦合性会变高
继承是静态的,不能再程序运行时发生改变
总的来说:
尽量使用对象组合,而不是继承来达到复用的目的。复用时要尽量使用组合/聚合关系(关联关系),少用继承。需要慎重使用继承复用。
创建型设计模式
1.单例模式
有两种模式
1.懒汉模式:在类的声明阶段直接声明一个静态成员变量并初始化赋空间
2.饿汉模式:做声明判断,当静态成员为null的时候初始化,当其不为null的时候直接给出这个静态成员。
懒汉模式直接不推荐,大概率会造成资源的浪费。饿汉模式的话,在多线程情况下可能会出问题,是线程不安全的。调用方法之前要判断有没有类的实例,如果没有,则创建并返回,如果有了就要返回之前的,而不是直接通过这个静态对象是否为null来判断,这个时候就需要枷锁来解决多线程下,单例不安全的问题。
实现起来也很简单,就创建一个lock来实现就可以了(c#),lock是c#的一个语法糖,相当于一个自带的mutex互斥锁,就不用自己写了。也就是说lock()锁定的代码段当有一个线程正在执行的时候会强行占用这一段,其他进程再来调用这段代码的时候就会进行阻塞,前一个线程结束这段执行的时候,这段代码才会放开,交由排队等待的线程执行这段代码。
new指令会执行三个命令 :
1.在内存中开辟空间
2.执行构造函数,创建对象
3.把空间只想我们创建的对象
一般是 执行 123,但是有时候可能会执行 132,这个时候这个new指令就有可能会出问题,当并发量大的时候,就有可能会出问题,会在执行1 3 2 的时候,有可能会返回一个错误的(空)对象
这个就不多说了,我在Active Server上用的全是这个单例模式,笑了(
简单工厂模式
这个比较蠢,没多大用,就是一个类给一个Init函数去初始化这个类,然后就可以在这个类内进行一些初始化运作,让这个类变成需要的指定的对象类。
不推荐这么用,因为有更好的方法可以完全继承这个方案的优点而摒弃它的缺点
不给代码示例了,其实这个有点像我第一次做的那个师生对讲,我当时想着让一个软件包含教师端和学生端的代码,然后通过命令行参数去确定打开的具体是什么软件,这显然是不可行的,如果这样做的话,那就是简单工厂模式
这个模式有几个很严重的问题:
1.不好维护,维护这个工厂的话必然会违反开闭原则,比如我想要修改一个启动项,那必然就修改了这个工厂的开闭原则了。也就是只增不改原则
2.这样学生端和教师端的代码就缠在一起,维护是一件非常麻烦的事,不仅对我,对任何一个人都非常不友好。
工厂模式
工厂模式的话 与那种简单工厂模式差不多,但是工厂里面是直接生出了一个类对象,就是工厂里面生产出产品,而不是工厂把自己变成产品
//日志记录器工厂
class LoggerFactory {
//静态工厂方法,工厂LoggerFactory生成了产品Logger
public static Logger createLogger(String args) {
if(args.equalsIgnoreCase("db")) {
//连接数据库,代码省略
//创建数据库日志记录器对象
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
else if(args.equalsIgnoreCase("file")) {
//创建日志文件
//创建文件日志记录器对象
Logger logger = new FileLogger();
//初始化文件日志记录器,代码省略
return logger;
}
else {
return null;
}
}
}
这个 感觉比较好理解,就是让一个工厂去初始化一个类,但是一个类既然有自己的构造函数,那为什么还需要一个工厂去做呢...
问题还是很明显的,就是还是需要大量的if else,维护这个接口必然会破坏开闭原则,所以不怎么用,知道有这么回事就行了
抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。
举个例子:
abstract class AbstractFactory {
public abstract AbstractProductA createProductA(); //工厂方法一
public abstract AbstractProductB createProductB(); //工厂方法二
……
class ConcreteFactory1 extends AbstractFactory {
//工厂方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
//工厂方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
……
与工厂方法模式一样,抽象工厂模式也可为每一种产品提供一组重载的工厂方法,以不同的方式对产品对象进行创建。
抽象工厂增加新的产品族(纵向)是符合开闭原则的,只需要新增新的工厂即可,而需要新增新的产品等级结构(横向)时则不符合开闭原则,这会需要在抽象工厂中新增一个抽象方法C,而修改了抽象工厂就会导致它的所有实现类都需要新增一个方法,这样是不符合开闭原则的。
增加产品族相当于增加一个新的子类(ConcreteFactory)去继承AbstractFactory,没有修改源代码。
增加产品等级结构相当于创建多一种产品,要在工厂类添加多一个方法createProduct()。
这样理解就行了
原型模式
使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。
这个我感觉就是深拷贝浅拷贝...这个不过多赘述了,我觉得c++程序员应该不会不懂...
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
总的来说,我感觉建造者模式就是给一个对象分块,比如一个人,他有四肢和脑袋,然后四肢和脑袋 各有不同又有很多细节,所以在造一个人的时候可以一部分一部分的构建,然后放到一起,我们管这个拼接起来的东西叫做人
class Product {
private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
abstract class Builder {
//创建产品对象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
七个结构型模式
适配器模式
将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
这个就比较像CLR,链接.net和c++的,中间件就叫Wrapper,这个我巨懂,不多说了
桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
在具体编码实现时,由于在桥接模式中存在两个独立变化的维度,为了使两者之间耦合度降低,首先需要针对两个不同的维度提取抽象类和实现类接口,并建立一个抽象关联关系。对于“实现部分”维度,典型的实现类接口代码如下所示:
interface Implementor {
public void operationImpl();
}
在实现Implementor接口的子类中实现了在该接口中声明的方法,用于定义与该维度相对应的一些具体方法。对于另一“抽象部分”维度而言,其典型的抽象类代码如下所示:
abstract class Abstraction {
protected Implementor impl; //定义实现类接口对象
public void setImpl(Implementor impl) {
this.impl=impl;
}
public abstract void operation(); //声明抽象业务方法
}
在抽象类Abstraction中定义了一个实现类接口类型的成员对象impl,再通过注入的方式给该对象赋值,一般将该对象的可见性定义为protected,以便在其子类中访问Implementor的方法,其子类一般称为扩充抽象类或细化抽象类(RefinedAbstraction),典型的RefinedAbstraction类代码如下所示:
class RefinedAbstraction extends Abstraction {
public void operation() {
//业务代码
impl.operationImpl(); //调用实现类的方法
//业务代码
}
}
对于客户端而言,可以针对两个维度的抽象层编程,在程序运行时再动态确定两个维度的子类,动态组合对象,将两个独立变化的维度完全解耦,以便能够灵活地扩充任一维度而对另一维度不造成任何影响。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)