设计模式 - 读书笔记

程序设计七大原则:

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()锁定的代码段当有一个线程正在执行的时候会强行占用这一段,其他进程再来调用这段代码的时候就会进行阻塞,前一个线程结束这段执行的时候,这段代码才会放开,交由排队等待的线程执行这段代码。

image

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();  //调用实现类的方法
		//业务代码
	}
}

对于客户端而言,可以针对两个维度的抽象层编程,在程序运行时再动态确定两个维度的子类,动态组合对象,将两个独立变化的维度完全解耦,以便能够灵活地扩充任一维度而对另一维度不造成任何影响。

posted @ 2022-11-30 11:09  轩先生。  阅读(50)  评论(0编辑  收藏  举报