设计模式

介绍

  • 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
  • 设计模式的本质提高 软件的维护性,通用性和扩展性,并降低软件的复杂度。
  • <<Design pattern>> 是经典的书,作者是Erich GammaRichard HelmRalph JohnsonJohn Vlissides Design(俗称 “四人GOF”)
  • 设计模式并不局限于某种语言,java,php,c++ 都有设计模式.

设计模式类型

设计模式分为三种类型,共23种

  • **创建型模式: **单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。

  • 结构型模式: 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

  • 行为型模式: 模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

注意:不同的书籍上对分类和名称略有差别

单例模式

单例设计模式介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类==只能存在一个对象实例==,并且该类只提供一个取得其对象实例的方法(静态方法)。

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。

单例设计模式八种方式 (下面只实现七种)

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式 ( 线程不安全)
  • 懒汉式 ( 线程安全,同步方法)
  • 懒汉式 ( 线程安全,同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举
饿汉式(静态常量)

应用实例

步骤如下:

  1. 构造器私有化 (防止 new )

  2. 类的内部创建对象

  3. 向外暴露一个静态的公共方法。getInstance

  4. 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//*饿汉式 (静态变量)
class Singleton {
//*1.构造器私有化,外部不能 new
private Singleton() {

}

//*2.本类内部创建对象实例
private final static Singleton instance = new Singleton();

//*3.对外提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}

}

优缺点说明:

  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
  3. 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果
  4. 结论:这种单例模式可用,可能造成内存浪费
饿汉式(静态代码块)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

//*饿汉式 (静态变量)
class Singleton {
//*1.构造器私有化,外部不能 new
private Singleton() {

}

//*2.本类内部创建对象实例
private static Singleton instance;

//* 在静态代码块中创建单例对象
static {
instance = new Singleton();
}

//*3.对外提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}

}


优缺点说明:

  1. 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

  2. 结论:这种单例模式可用,但是可能造成内存浪费

懒汉式(线程不安全)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//* 懒汉式(线程不安全)
class Singleton {
private static Singleton instance;

private Singleton() {
}

//* 提供静态的公有方法,当使用该方法时才创建instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

}

优缺点说明:

  1. 起到了Lazy Loading的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了if (instance== null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
  3. 结论:在实际开发中,不要使用这种方式
懒汉式(线程安全,同步方法)
1
2
3
4
5
6
7
8
// 修改三方代码

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

优缺点说明:

  1. 解决了线程不安全问题
  2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
  3. 结论:在实际开发中,不推荐使用这种方式
双重检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//* 双重检查
class Singleton {
private static volatile Singleton instance;

private Singleton() {
}

//* 加入双重检查代码,解决线程安全问题,同时解决懒加载问题
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance==null) {
instance = new Singleton();
}
}
}
return instance;
}

}

优缺点说明:

  1. Double-Check (双重检查) 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次**if (singleton == null)**检查,这样就可以保证线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步。
  3. 线程安全;延迟加载;效率较高
  4. 结论:在实际开发中,推荐使用这种单例设计模式
静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//* 静态内部类
class Singleton {
private static volatile Singleton instance;

private Singleton() {
}

//* 静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton();
}

//* 提供一个静态公有方法 直接返回SingletonInstance.INSTANCE
public static synchronized Singleton getInstance() {
return SingletonInstance.INSTANCE;
}

}

优缺点说明:

  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  5. 结论:推荐使用.
枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance==instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
}
}


enum Singleton{
INSTANCE;
public void sayOK(){
System.out.println("ok");
}
}

优缺点说明:

  1. 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
  2. 这种方式是Effective Java作者Josh Bloch 提倡的方式
  3. 结论:推荐使用
单例模式注意事项和细节说明
  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

工厂模式

例子

1
2
3
4
5
6
7
1.还没有工厂时代:假如还没有工业革命,如果一个客户要一款宝马车,一般的做法是客户去创建一款宝马车,然后拿来用。
2.简单工厂模式:后来出现工业革命。用户不用去创建宝马车。因为客户有一个工厂来帮他创建宝马.想要什么车,这个工厂就可以建。比如想 要320i系列车。工厂就创建这个系列的车。即工厂可以创建产品。
3.工厂方法模式时代:为了满足客户,宝马车系列越来越多,如320i,523i,30li等系列一个工厂无法创建所有的宝马系列。于是由单独分出 来多个具体的工厂。每个具体工厂创建一种系列。即具体工厂类只能创建一个具体产品。但是宝马工厂还是个抽象。你需要指定某个具体的 工厂才能生产车出来。
4.抽象工厂模式时代:随着客户的要求越来越高,宝马车必须配置空调。而且这空调必须对应给系列车才能使用。于是这个工厂开始生产宝马 车和需要的空调。
最终是客户只要对宝马的销售员说:我要523i空调车,销售员就直接给他523i空调车了。而不用自己去创建523i空调车宝马车.
(我只是举个例子,说到宝马配置空调完全是为了举例,甚至有点扯,哪有车和空调必须对应才能使用啊)
这就是工厂模式。

​ 工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

工厂模式可以分为三类:

  1. 简单工厂模式(Simple Factory)
  2. 工厂方法模式(Factory Method)
  3. 抽象工厂模式(Abstract Factory)

这三种模式从上到下逐步抽象,并且更具一般性。
GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。

将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

区别:
工厂方法模式:

  • 一个抽象产品类,可以派生出多个具体产品类。
  • 一个抽象工厂类,可以派生出多个具体工厂类。
  • 每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:

  • 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
  • 一个抽象工厂类,可以派生出多个具体工厂类。
  • 每个具体工厂类可以创建多个具体产品类的实例。

区别:

  • 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
  • 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。两者皆可。

简单工厂模式

建立一个工厂(一个函数或一个类方法)来制造新的对象。
分布说明引子:从无到有。客户自己创建宝马车,然后拿来用。

image-20200709195558379

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


public class BMW320 {
public BMW320(){
System.out.println("制造-->BMW320");
}
}

public class BMW523 {
public BMW523(){
System.out.println("制造-->BMW523");
}
}

public class Customer {
public static void main(String[] args) {
BMW320 bmw320 = new BMW320();
BMW523 bmw523 = new BMW523();
}
}

客户需要知道怎么去创建一款车,客户和车就紧密耦合在一起了.为了降低耦合,就出现了工厂类,把创建宝马的操作细节都放到了工厂里面去,客户直接使用工厂的创建工厂方法,传入想要的宝马车型号就行了,而不必去知道创建的细节.这就是工业革命了:简单工厂模式

即我们建立一个工厂类方法来制造新的对象。如图:

1335407364_5745

产品类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class BMW {
public BMW(){

}
}

public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Factory {
public BMW createBMW(int type) {
switch (type) {

case 320:
return new BMW320();

case 523:
return new BMW523();

default:
break;
}
return null;
}
}

客户类:

1
2
3
4
5
6
7
public class Customer {
public static void main(String[] args) {
Factory factory = new Factory();
BMW bmw320 = factory.createBMW(320);
BMW bmw523 = factory.createBMW(523);
}
}

简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
先来看看它的组成:

  1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品

  2. 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。

  3. 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。

​ 下面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式。当客户不再满足现有的车型号的时候,想要一种速度快的新型车,只要这种车符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了。所以对产品部分来说,它是符合开闭原则的;但是工厂部分好像不太理想,**因为每增加一种新型车,都要在工厂类中增加相应的创建业务逻辑(createBMW(int type)方法需要新增case),这显然是违背开闭原则的。**可想而知对于新产品的加入,工厂类是很被动的。对于这样的工厂类,我们称它为全能类或者上帝类。

​ 我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝累坏了,也累坏了我们这些程序员。
​ 于是工厂方法模式作为救世主出现了。 工厂类定义成了接口,而每新增的车种类型,就增加该车种类型对应工厂类的实现,这样工厂的设计就可以扩展了,而不必去修改原来的代码。

工厂方法模式

​ 工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
工厂方法模式组成:

  1. 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。

  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

​ 工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来——当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有 的代码。可以看出工厂角色的结构也是符合开闭原则的!

1335434907_6359

代码如下:

产品类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class BMW {
public BMW(){

}
}
public class BMW320 extends BMW {
public BMW320() {
System.out.println("制造-->BMW320");
}
}
public class BMW523 extends BMW{
public BMW523(){
System.out.println("制造-->BMW523");
}
}

创建工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface FactoryBMW {
BMW createBMW();
}

public class FactoryBMW320 implements FactoryBMW{

@Override
public BMW320 createBMW() {

return new BMW320();
}

}
public class FactoryBMW523 implements FactoryBMW {
@Override
public BMW523 createBMW() {

return new BMW523();
}
}

客户类:

1
2
3
4
5
6
7
8
9
public class Customer {
public static void main(String[] args) {
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
BMW320 bmw320 = factoryBMW320.createBMW();

FactoryBMW523 factoryBMW523 = new FactoryBMW523();
BMW523 bmw523 = factoryBMW523.createBMW();
}
}

工厂方法模式仿佛已经很完美的对对象的创建进行了包装,使得客户程序中仅仅处理抽象产品角色提供的接口,但使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。

抽象工厂模式

产品等级结构:

​ 产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。

产品簇:

​ 在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。

4240218-087182060612e009

随着客户的要求越来越高,宝马车需要配置空调。于是这个工厂开始生产宝马车和配置需要的空调。这时候工厂有二个系列的产品:宝马车和空调.宝马车必须使用对应的空调才能使用.这时候分别使用一个车工厂和一个空调工厂都不能满足我们的需求,我们必须确认车跟空调的对应关系。因此把车工厂跟空调工厂联系在一起。因此出现了抽象工厂模式。
可以说,抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。
抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,

而且使用抽象工厂模式还要满足一下条件:

  1. 系统中有多个产品族,而系统一次只可能消费其中一族产品。
  2. 同属于同一个产品族的产品以其使用。

抽象工厂模式的各个角色(和工厂方法一样):

  1. 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。

  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。

  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。

1335434941_2951

产品类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//发动机以及型号  
public interface Engine {

}
public class EngineA extends Engine{
public EngineA(){
System.out.println("制造-->EngineA");
}
}
public class EngineB extends Engine{
public EngineB(){
System.out.println("制造-->EngineB");
}
}

//空调以及型号
public interface Aircondition {

}
public class AirconditionA extends Aircondition{
public AirconditionA(){
System.out.println("制造-->AirconditionA");
}
}
public class AirconditionB extends Aircondition{
public AirconditionB(){
System.out.println("制造-->AirconditionB");
}
}

创建工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//创建工厂的接口  
public interface AbstractFactory {
//制造发动机
public Engine createEngine();
//制造空调
public Aircondition createAircondition();
}


//为宝马320系列生产配件
public class FactoryBMW320 implements AbstractFactory{

@Override
public Engine createEngine() {
return new EngineA();
}
@Override
public Aircondition createAircondition() {
return new AirconditionA();
}
}
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {

@Override
public Engine createEngine() {
return new EngineB();
}
@Override
public Aircondition createAircondition() {
return new AirconditionB();
}



}

客户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class Customer {
public static void main(String[] args){
//生产宝马320系列配件
FactoryBMW320 factoryBMW320 = new FactoryBMW320();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();

//生产宝马523系列配件
FactoryBMW523 factoryBMW523 = new FactoryBMW523();
factoryBMW320.createEngine();
factoryBMW320.createAircondition();
}
}
总结:
  • 抽象工厂模式的扩展有一定的“开闭原则”倾斜性。
  • 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则;
  • 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
  • 另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
  • 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
  • 将工厂抽象成两层,Abstract Factory(抽象工厂) 和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。

原型模式

**例子:**现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊 属性完全相同的10只羊。

传统方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class Sheep{
private String name;
private Integer age;
private String color;
}

public class Client {

public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 18, "白色");
Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());

System.out.println(sheep);
System.out.println(sheep1);
System.out.println(sheep2);

}
}

原型模式:

Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 => 原型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Sheep implements Cloneable{
private String name;
private Integer age;
private String color;
public Sheep friend;

/**
* 克隆该实例,使用默认的clone方法来完成
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() {
Sheep sheep=null;
try {
sheep =(Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}


public class Client {

public static void main(String[] args) {
Sheep sheep = new Sheep("tom", 18, "白色");
sheep.friend=new Sheep("jack", 25, "蓝色");

Sheep sheep1 = (Sheep) sheep.clone(); //克隆
Sheep sheep2 = (Sheep) sheep.clone(); //克隆

System.out.println(sheep);
System.out.println(sheep1);
System.out.println(sheep2);

}
}

基本介绍:

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
  4. 形象的理解:孙大圣拔出猴毛, 变出其它孙大圣

浅拷贝的介绍:

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
  3. 前面我们克隆羊就是浅拷贝
  4. 浅拷贝是使用默认的 clone()方法来实现 sheep = (Sheep) super.clone();

深拷贝基本介绍:

  1. 复制对象的所有基本数据类型的成员变量值
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
  3. 深拷贝实现方式1:重写clone方法来实现深拷贝
  4. 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class DeepProtoType implements Serializable, Cloneable{

public String name; //String 属性
public DeepCloneableTarget deepCloneableTarget;// 引用类型


//深拷贝 - 方式 1 使用clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {

Object deep = null;
//这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();

return deepProtoType;
}

//深拷贝 - 方式2 通过对象的序列化实现 (推荐)

public Object deepClone() {

//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;

try {

//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出

//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();

return copyObj;

} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}

}

}

原型模式的注意事项和细节:

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  4. 在实现深克隆的时候可能需要比较复杂的代码
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则

建造者模式

盖房项目需求:

  1. 需要建房子:这一过程为打桩、砌墙、封顶
  2. 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的.

传统方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public abstract class AbstractHouse {

//打地基
public abstract void buildBasic();

//砌墙
public abstract void buildWalls();

//封顶
public abstract void roofed();

public void build() {
buildBasic();
buildWalls();
roofed();
}

}

public class CommonHouse extends AbstractHouse {

@Override
public void buildBasic() {
System.out.println("普通 打地基");
}

@Override
public void buildWalls() {
System.out.println("普通 砌墙");
}

@Override
public void roofed() {
System.out.println("普通 封顶");
}
}

建造者模式基本介绍

  1. 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
  2. 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

建造者模式的四个角色

  1. Product(产品角色): 一个具体的产品对象。
  2. Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。
  3. ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
  4. Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,
    • 一是:隔离了客户与对象的生产过程,
    • 二是:负责控制产品对象的生产过程。

建造者模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//产品
public class House {
private String baise;
private String wall;
private String roofed;

}

//抽象的建造者
public abstract class HouseBuilder {

protected House house=new House();

//打地基
public abstract void buildBasic();

//砌墙
public abstract void buildWalls();

//封顶
public abstract void roofed();

public House buildHose() {
return house;
}
}


public class CommonHouseBuilder extends HouseBuilder {

@Override
public void buildBasic() {
System.out.println("普通 打地基");
}

@Override
public void buildWalls() {
System.out.println("普通 砌墙");
}

@Override
public void roofed() {
System.out.println("普通 封顶");
}

}


public class HighHouseBuilder extends HouseBuilder {

@Override
public void buildBasic() {
System.out.println("高楼 打地基");
}

@Override
public void buildWalls() {
System.out.println("高楼 砌墙");
}

@Override
public void roofed() {
System.out.println("高楼 封顶");
}
}

//指挥者,指定制作流程,返回
public class HouseDirector {

HouseBuilder houseBuilder = null;

//构造器传入houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}

public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}

//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHose();
}
}

建造者模式原理类图

image-20200710215122866

建造者模式的注意事项和细节

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象

  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象

  3. **可以更加精细地控制产品的创建过程 **。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程

  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”

  5. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,**如果产品之间的差异性很大,则不适合使用建造者模式,**因此其使用范围受到一定的限制。

  6. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.

  7. 抽象工厂模式VS建造者模式
    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。

    而建造者模式则是要求按照指定的蓝图建造产品,它的主要
    目的是通过组装零配件而产生一个新产品

适配器模式

基本介绍

  1. 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  2. 适配器模式属于结构型模式
  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  2. 从用户的角度看不到被适配者,是解耦的
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法

类适配器

基本介绍:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。

以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电
相当于src (即被适配者),我们的目dst(即 目标)是5V直流电

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Voltage220V {

public int output220V() {
int src = 220;
System.out.println("电压=" + src);
return src;
}

}




public class VoltageAdapter extends Voltage220V implements IVoltage5V {

@Override
public int output5V() {
//获取到220v
int src = output220V();
//处理过后的电压
int dstV=src/44; // 转成5v
return dstV;
}
}

public interface IVoltage5V {

public int output5V();
}


public class Phone {

//充电
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V()==5){
System.out.println("5V");
}else {
System.out.println("电压过高");
}
}

}


public static void main(String[] args) {
//* 类适配器
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
类适配器模式注意事项和细节
  1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有一定局限性;
  2. rc类的方法在Adapter中都会暴露出来,也增加了使用的成本。
  3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器

  1. 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,完成src->dst的适配
  2. 根据**“合成复用原则”**,在系统中尽量使用关联关系来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class VoltageAdapter implements IVoltage5V {

private Voltage220V voltage220V; // 关联关系的聚合

public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}

@Override
public int output5V() {
int dstV=0;
if (voltage220V!=null){
//获取到220v
int src = voltage220V.output220V();
//处理过后的电压
dstV = src / 44; // 转成5v
}
return dstV;
}
}


对象适配器模式注意事项和细节
  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
  2. 使用成本更低,更灵活。

接口适配器模式

接口适配器模式介绍
  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
  2. 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  3. 适用于一个接口不想使用其所有的方法的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public interface interface4 {

public void m1();

public void m2();

public void m3();

public void m4();
}


//* 在AbsAdapter 将interface4的方法默认实现
public abstract class AbsAdapter implements interface4 {

@Override
public void m1() {

}

@Override
public void m2() {

}

@Override
public void m3() {

}

@Override
public void m4() {

}
}


public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
@Override
public void m1() {
System.out.println("使用了m1");
}
};
absAdapter.m1();
}