设计模式、系统设计 record part03

创建者模式

1.创建、使用,二者分离

2.降低,耦合度

3.使用者,不用关注,对象的创建细节


工厂模式:

1.对象由工厂生产,

2.使用者与工厂交流,不与对象直接打交道,

3.在工厂里直接更换对象即可

4.工厂模式最大的优点解耦


简单工厂模式

下面是一个没有使用简单工厂模式的案例,

1.图中箭头+虚线表示,依赖关系,is 临时关联,is 最弱的关联方式,只是去使用虚线箭头指向的类,

2.实线+空心三角形表示,继承关系,被指的是父类,

在要显式UML图形的类上右键,并选择diagrams/show diagram

然后将相关的类一起拽到右侧的show diagram窗口内,

得到如下的UML图形,

一个抽象的咖啡类,是父类,代码如下

一个子类,继承父类(咖啡类),代码如下

另一个子类,继承父类(咖啡类),代码如下

咖啡商店这个类,代码如下

一个测试类 client,代码如下

输出测试结果,如下

用简单工厂去改进包含咖啡店类的点餐系统,

箭头+虚线表示,依赖关系,is 临时关联,is 最弱的关联方式,只是去使用虚线箭头指向的类,CoffeStore类去使用SimpleCoffeFactory类,SimpleCoffeFactory类去使用Coffee类,传递的结果是,CoffeStore类使用了Coffee类,

改进后,涉及的类,如下

生成UML图形的方法如上,不再赘述,得到类图如下

较之没有使用简单工厂时,类图里多了一个SimpleCoffeFactory类,

SimpleCoffeFactory类,代码如下

里面的代码其实就是之前写在CoffeStore类的生成coffee的代码,被移植到SimpleCoffeFactory类里,

CoffeStore类的代码是关键,如下

拿咖啡的时候,CoffeStore调用SimpleCoffeFactory类,

SimpleCoffeFactory再去调用Coffee类

看起来,SimpleCoffeFactory类就是CoffeStore类的中介

针对使用简单工厂改进后的一个测试类 client,代码如下

测试结果,如下

这句话

应该是说修改工厂类的客户端的代码就可以,


静态工厂模式

这样,再拿咖啡(获取Coffee的对象),就不需要客户端去new了,可以通过工厂类调方法来获得Coffee的对象,

静态工厂模式下的工厂类代码和简单工厂模式下的工厂类代码大体一致,唯一不同的是静态工厂模式下的工厂类中方法被static所修饰,如下

另外,在静态工厂模式下与工厂类的客户端的代码也需要做相应的调整,

即在简单工厂模式下工厂类的客户端通过new来获得工厂类的对象,然后再用这个对象去调用方法来获取咖啡(2步得到)产品,

静态工厂模式下的工厂类的客户端通过使用工厂类调自己方法的方式直接就可以获得咖啡(1步得到)产品了,如下

静态工厂和简单工厂两个模式,在获取工厂类对象的时候,最大的区别是:

1.静态工厂模式下,使用工厂类名调用其方法来直接获得咖啡产品

2.简单工厂模式下,分2步,一、先new出来一个工厂类的对象,二、使用这个工厂类的对象去调用它所属类的方法来获得咖啡产品


工厂方法模式


工厂方法模式可以解决简单工厂模式中存在的问题,

上图提到的缺点指的是简单工厂模式中存在的问题,如下

1.虚线+箭头,依赖关系,表示使用,临时的

2.实线+空心三角形,继承关系

3.实线+箭头,单向关联,表示引用,长期的

注意,关联关系与依赖关系的区别在于,
依赖关系是一种临时的关系,依赖关系主要体现在方法参数,当调用方法时才有关系,
关联关系是一种长期的关系,主体现在成员变量,无论是否调用方法这种关系都存在

工厂方法 Factory Method,本例涉及的类和接口有,如下

UML,类图结构,如下

一个具体产品类,AmericanCoffee,它继承自Coffee类,代码如下


一个抽象产品类,抽象的父类,Coffee,代码如下


一个抽象工厂类, 接口,CoffFactory,代码如下


一个具体工厂类,AmericanCoffeeFactory,继承自接口 CoffeeFactory,代码如下




虚线+箭头,依赖关系,表示使用,临时的

AmericanCoffeeFactory里new了一个具体产品类AmericanCoffee的对象(通过具体工厂得到了具体产品),
一个具体工厂类,latteCoffeeFactory,去implement实现接口 CoffeeFactory,代码如下

latteCoffeeFactory里new了一个具体产品类latteCoffee的对象(通过具体工厂得到了具体产品),
一个同时依赖抽象工厂和抽象产品的类,CoffeeStore,代码如下

注意,咖啡是在咖啡店调用自己的方法'创建咖啡'时,才生产出来。而且咖啡店的这个'创建咖啡'的方法里面又调用了咖啡工厂的'创建咖啡'的方法才得到了咖啡。嵌套调用,咖啡店调用自己的方法,方法里面又调用了工厂的方法。

虚线+箭头,依赖关系,表示使用,临时的

根据工厂方法设计了一个测试类,如下

测试结果,如下

注意,测试方法中调用了咖啡店的orderCoffee方法,里面有两个方法

加糖、加奶,这两个并不是具体咖啡实现类的方法,如下图

而是咖啡具体实现类的父类Coffee中定义的2个方法,如下图

这说明,一旦调用了子类中没有的方法,就会向上去子类的父类中寻找,如果找到就直接用,如果没有就报错。

好了,到这里就能看出工厂方法能解决简单工厂的问题了,

如果要增加产品,让新产品类去继承父类Coffee,同时,再做一个新产品对应的具体工厂类(任务是去实现接口CoffeeFactory),其他的代码一律不用动,

这样就完美的解决了,简单工厂模式中存在的问题,如下


抽象工厂模式

steptember2024the30thMonday

等级,在横轴上任一点表示一个种类(电脑、手机)
,在纵轴上任意一点表示一个品牌(apple、Huawei)

一、在轴上先找1个点(0,1),确定一个品

二、在轴上再找2个点(0,1)和(1,1),可以确定一个品牌下的2种类产品

抽象工厂模式中,

电脑、手机这样不同种类的产品,用等级表示,

apple、Huawei这样的不同品牌,用表示,

可以换个理解方式,把等级(品种)理解成尺寸,

cpp 复制代码
花团锦簇

就是一团,一个团体,就是一个公司一个品牌

这一团里的花,有大有小,这就是等级

等级里,把电脑看成是尺寸大的产品,把手机看成是尺寸小的产品,这样理解等级就很容易了,

再或者拿咖啡的例子套过来也是很好理解的,簇就是咖啡品牌(美式、拿铁),等级就是尺寸(大、中、小)

cpp 复制代码
纵轴上的点表示品牌,即簇
横轴上的点表示尺寸,即等级

所以,在工厂方法模式下,没有等级(大杯、中杯、小杯),只有(美式、拿铁等品牌),工厂方法模式看起来是一条线,你只能选品牌(簇),不能选尺寸(等级),这样就不灵活了,

而抽象工厂模式下,有等级(尺寸)、有簇(品牌),是一个面,所以非常的灵活,可以定制不同品牌(簇)的不同尺寸(等级)的产品,比如大杯的拿铁、小杯的美式......

这里再回顾一下工厂方法模式的UML类图,进行充分的对比,如下

通过UML类图对比可见,"容易发生类爆炸"指的是与产品类对应的工厂类爆炸,抽象工厂减少了工厂类的数量,即让工厂可以按簇(跨类跨等级)多样化生产,一个工厂类可以生产多个等级的产品,而不必给每个等级都设置一个工厂类,避免了类爆炸

October2024the03rdWednesday

注意,这里的抽象工厂(接口)和具体工厂之间是实现关系使用虚线+空心三角形进行连接,如下:

而,具体工厂和具体产品之间是依赖关系(即使用关系),使用虚线+箭头进行连接,如下:

具体产品类和抽象产品类之间是继承关系,继承关系使用实线+空心三角形进行连接,如下:

这里对上一章的UML类图画法内容做一个小小的总结,

1,实现关系是对接口 interface 而言,虚线+空心三角

  1. 继承关系是对类(父类) class 而言,实线+空心三角
so

继承是继承自父类,实打实的真实父子关系,所以用实线
实现,是实现接口,接口是抽象的无形的,所以是虚线

抽象工厂相关类和接口,如下:

抽象工厂的 UML 类图,如下:

一个具体产品类,AmericanCoffee,代码如下:

甜品产品的抽象父类,Dessert,,代码如下:

一个甜品产品的具体类,Trimisu,代码如下:

另一个甜品产品的具体类,MatchaMousse,代码如下

可以同时生产甜品和咖啡2种产品的抽象工厂(接口),代码如下:

一个具体的工厂类,ItalyDessertFactory,如下:

可以看到,这个是意大利风味的具体工厂(去实现了接口DessertFactory),可以生产不同等级(品种)的产品(咖啡、甜点),意式:拿铁咖啡、意式:提拉米苏

另一个具体的工厂类,AmericanDessertFactory,代码如下:

这个是按(品牌)划分的,美国风味的具体工厂(去实现了接口DessertFactory),可以生产不同等级(品种)的产品(咖啡、甜点),美式咖啡、美式:抹茶慕斯

一个测试类,client,代码如下:

运行结果,如下:

其中,

是通过,

得来的,

因为,

coffee来自美式工厂,所以coffee.getName()必定打印的是如下结果,

而结果

来自,语句

dessert 也来自美式工厂,生产的是美式甜点,所以,必定打印的是如下结果


原型模式

1.copy 一个实例(对象),一个新实例(对象),新旧两个对象是两个独立的对象,但引用类属性是共用的,其实就是浅拷贝。

2.接口Prototype的方法clone()必须被实现,

Realizetype类去实现接口Prototype的方法(clone()),这个方法能得到Realizetype类的对象,即拷贝对象,

3.可以通过new1个Realizetype类来得到Realizetype类的对象,new之后,用方法clone()可以获得对象的拷贝,

Realizetype类中的方法(clone())可以复制上一步中 new出来的 Realizetype类的对象,获得一个拷贝,注意这样操作后得到的是浅拷贝

原型模式主要用在,对象的克隆,获得的是浅拷贝

cpp 复制代码
深克隆、浅克隆

Java中的 Object 类提供了浅克隆的方法 clone()。

cpp 复制代码
首先,要明确一个概念,属性的类型,属性就是变量,
变量的类型有2大种,
1.基本类型,包括:整型、布尔型、字符串型等等,
2.非基本类型,即引用类型,包括:对象、数组、列表等,
所以当属性是引用类型比如是对象时就涉及到内存地址了,
浅克隆时,新对象和原对象中的引用类型的属性
用的是同一个内存地址里的值,
所以浅克隆中的引用类型的变量(属性)是
被新旧对象二者共用的,
换句话说,在浅拷贝的时候,
虽然新对象和原对象已经不是同一个对象了,
但是,它们的引用类型的属性仍然是共用的,是同一个属性,
这样的话
改动新对象或者旧对象中的引用类型的属性,
另一个对象会受影响,引用类型的属性会跟着变动,
也就是说2者用的是同一个属性(引用型),因为二者拿到的指针是同一个指针,既然是同一个指针,那必定指向同一个内存地址,所以二者使用着同一个内存地址中的值,

还需要注意的是,新、旧对象中的基本类对象是各用各的,互不干扰(实验二test2中有介绍),

因为基本类型的数据,都是各自有独立的内存空间的,

比如,a=2,b=2,虽然都是2,但这两个 2,都有各自的内存空间,是两个独立的 2,这就是基本类型的数据的特点

原型模式实验涉及到的4个实验有,如下:

浅拷贝-实验一demo,这个实验中的类只有方法,没有属性(变量),一个简简单单的浅拷贝例子,demo里面的类有,如下:

Realizetype类对应的代码,如下:

上图中,Realizetype 实现了 Cloneable接口,如下:

而Realizetype类中,super.clone();指的是Realizetype类的父类 Object类的方法 clone(),

,如下,

注意,下图中提到的"复制成功"指的是,

克隆了当前的对象,即下面代码中刚刚创建完成的原型对象(实际上并没有给出克隆的相关代码) ,如下

因为,父类的方法得到的是一个Object类型,

需要强转成 Realizetype 类型,如下:

demo里的测试类,client,代码如下:

1.new了一个Realizetype对象,如下:

会打印,如下图中红框内容:

2.用new出来的Realizetype对象调用其自己的方法clone(),得到一个拷贝对象,如下:

3.对比,new出来的、clone()出来的,这两个对象是否是同一个对象,如下:

测试需经过如下:

结果中可以看到,new出来对象和clone()出来的对象不是同一个对象,

注意,这是在去实现接口Cloneable的前提下,能得到结果,如下:

如果在不实现接口Cloneable的情况下,代码如下:

这时,再运行测试类 client,得到结果如下:

有抛出异常的报错,即下面代码运行时抛出异常,

因此导致,下面的代码无法执行,

因为,我们没有让Realizetype它去实现接口Cloneable,所以,去调用父类的方法super.clone()就会报错抛异常,

Cloneable接口:实现此接口则可以使用java.lang.Object 的clone()方法,否则会抛出CloneNotSupportedException 异常

所以,正确的写法是,去继承接口 Cloneable,如下:

所以要想实现克隆操作,就必须去实现接口Cloneable,

下面,加入两行代码,从另一个角度查看,new出来的和clone()出来的是不是同一个对象,如下:

结果如下:

可以看到,new出来的和clone()出来的2个对象的内存地址并不相同,所以不是同一个对象。注意这还是浅拷贝,一个没有属性的浅拷贝,

对任何的对象x,都有x.clone() !=x 因为克隆对象与原对象不是同一个对象

浅拷贝-实验二test2,类里有属性,并且属性类型是基本类型,test2里的类有,如下:

其中citation类,代码如下:

注意,这个实验中的类,已经有属性了,name是基本类型的属性,string字符串类型的属性,

要克隆这个类,就要让这个类去实现接口Cloneable,如下

最后,再用调用父类 Object 的clone()方法,如下

同时,进行了强制转换,转换成自己的类

这样就可以完成对象的复制了,
test2里的测试类,CitationTest,代码如下:

测试类CitationTest运行结果如下:

可见,new出来的c1和clone()出来的c2不是同一个对象,它们各自带着自己的name属性,互不干扰,这个例子还是浅拷贝,

在浅克隆中,基本类型的变量分属于不同的对象,互不干扰,

对任何的对象x,都有x.clone() !=x 因为克隆对象与原对象不是同一个对象

浅拷贝-实验三test,带有引用型的属性,test里的类有,如下:

类 Citation 实现接口 Cloneable,代码如下:

注意,这个实验里,有引用型属性, stu,其类型是 Student 类,

test类的测试类,CitationTest,代码如下:

test测试类CitationTest运行,结果如下:

这里可以看到,在浅拷贝时,新旧对象中的引用类型属性是同一个属性,改动一个对象中的引用类型属性,另一个对象中的引用类型属性跟着变化。因为,在浅拷贝中,新旧对象中的引用类型属性使用同一个内存地址空间,
下面这个实验四设计使用了序列化+反序列实现深克隆,代码变得不易理解,阅读有难度,

序列化实现深克隆。

概念:

序列化:把对象写到流里

反序列化:把对象从流中读出来

在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。

实验四test1,带有引用型的属性,test1里的类有,如下:

测试类,CitationTest,代码如下:

测试结果如下:

引用类型 属性stu 所属的类 Student,代码如下:

实现接口 Cloneable 的类 Citation,代码如下,

其实深拷贝比浅拷贝只是多复制了一次引用型属性,让新旧两个对象中的所有的属性(基本类型+非基本类型)都互不干扰,各用各的,仅此而已,

深拷贝,就是将属性彻底隔开,让新旧两个对象不再有任何牵连,是完完全全的拷贝,新旧两个拷贝是完全独立的2个个体,彼此之间没有任何共用的内存地址空间。

浅拷贝的时候,除了新旧对象是独立的,它们的基本类型属性是独立的,除此之外其他的引用类型的属性就不是独立的了,是被新旧两个对象同时使用的,因为新旧两个对象中的引用类型属性使用的是同一块内存地址,被新旧对象同时调用,新旧对象中的引用型属性,相当于共享单车,新旧对象都能骑,一个把车弄没电了,另一个也就骑不了了。

深拷贝的另一种实现方法是:将引用类型的属性,也clone()一次,下面的例子里属性是student

cpp 复制代码
t = (Teacher) super.clone();
    t.student = (Student)student.clone()

再多说两句,基础类型里存放的是value,值是final的,a=2,同时也可以 b=2,每个2都有自己独立的内存空间,所以都是独立的,而非基础类型(引用型)存放的是指针,之所以叫引用型,说的就是通过存放的指针找到储存在别处的value,


建造者模式




聚合关系用菱形表示(空心菱形)

与聚合关系对应的是组合关系,

October2024the05thSaturday

cpp 复制代码
1.Product 是一个复杂的产品,因为它有很多属性,
2.继承 builder 的 concreteBuilder 提供
去改变(创建、生成) > Product 的各个属性的 能力,
3,builder  有不同的 concreteBuilder  ,
每个 concreteBuilder   都有改变 Product 各个属性的能力,
且每个 concreteBuilder 对 Product 各个属性所做修改的
结果是不同的,花样不同,
4.Director,来指定使用 builder 的哪个 concreteBuilder ,
同时, 然后调用被指定的 concreteBuilder 所具有的能力去
改变创建、生成) Product 的各个属性,
最后,拿到一个具有很多个属性的复杂产品 Product 。
(但Director也不是直接拿到,
而是通过调用builder的方法间接拿到的)
注意,new一个Product对象和返回Product对象都是在
抽象父类builder  中定义的,
而 Product对象 的各个属性都是由concreteBuilder  
来修改的,
换句话说,
concreteBuilder  只改变 Product对象的各个属性,
builder  才是生成和向外提供 Product对象的家伙,
当然,向外返回 Product对象的具体代码 即
return product
也可以写在 concreteBuilder 里,
但其父类 builder  必须有对应的
即向外提供Product对象抽象方法
也就是说,抽象父类builder  提供Product对象,
而Product对象的各个属性(具体细节)由
子类concreteBuilder  去改变,
老子和儿子各干各的,分工明确,
其实,老子有修改Product对象的各个属性的方法
即抽象方法,只不过是外包给了子类concreteBuilder  
即去 override 父类的抽象方法来具体操作
Product对象的各个属性

建造者模式,涉及的实验类有,如下:

它的UML类图,如下:

demo1中 一个具体建造者类 MobileBuilder ,代码如下:

demo1中的抽象建造者类 Builder,代码如下:

demo1中的指挥类 Director,代码如下:

指挥类 Director 的私有属性 builder 是concreteBuilder 的父类的对象,

因此可以通过 向指挥类 Director 的构造方法 Director 传入 子类 concreteBuilder 给它的父类对象 builder 赋值,

如下:

demo1的测试类 client,代码如下:

测试结果,如下:

因为传入的是 MobileBuilder ,所以得到的是上面的测试结果,

MobileBuilder 代码如下:

实验结果和类定义一致。

使用创建者模式的另一个场景,开发场景

向构造函数传参容易出错,与之对应的代码截图如下:

改进的案例 demo2,涉及的类如下:

demo2中的 phone,代码如下:

Phone类,代码如下:

可见 Phone 类使用了创建者模式类 Builder,

Builder 在Phone 类里,是Phone 类的内建类

代码如下:

demo2的测试类 client,代码如下:

输出结果如下:

关于输出结果,Phone 类中还有一个 toString()方法,如下:


Cloneable接口:

官方解释:

1:实现此接口则可以使用java.lang.Object 的clone()方法,否则会抛出CloneNotSupportedException 异常

2:实现此接口的类应该使用公共方法覆盖clone方法

3:此接口并不包含clone 方法,所以实现此接口并不能克隆对象,这只是一个前提,还需覆盖上面所讲的clone方法。

cpp 复制代码
public interface Cloneable {
}

Object里面的Clone()方法:

1.clone()方法返回的是Object类型,所以必须强制转换得到克隆后的类型

2.clone()方法是一个native方法,而native的效率远远高于非native方法,

3.可以发现clone方法被一个Protected修饰,所以可以知道必须继承Object类才能使用,而Object类是所有类的基类,也就是说所有的类都可以使用clone方法

cpp 复制代码
protected native Object clone() throws CloneNotSupportedException;

October2024the05thSaturday

相关推荐
杨充17 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充18 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充18 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充18 小时前
2.面向对象的特性
设计模式
杨充18 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充18 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充18 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯21 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式