设计模式(Design pattern)之一

image

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式简介

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

什么是 GOF(四人帮,全拼 Gang of Four)?

在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。

四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。

  • 对接口编程而不是对实现编程。
  • 优先使用对象组合而不是继承。

设计模式的使用

设计模式在软件开发中的两个主要用途。

开发人员的共同平台

设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。

最佳的实践

设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。

设计模式的类型

根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。

序号 模式 & 描述 包括
1 创建型模式这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式(Factory Pattern)、抽象工厂模式(Abstract Factory Pattern)、单例模式(Singleton Pattern)、建造者模式(Builder Pattern)、原型模式(Prototype Pattern)
2 结构型模式这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、过滤器模式(Filter、Criteria Pattern)、组合模式(Composite Pattern)装饰器模式、(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)、代理模式(Proxy Pattern)
3 行为型模式这些设计模式特别关注对象之间的通信。 责任链模式(Chain of Responsibility Pattern)、命令模式(Command Pattern)、解释器模式(Interpreter Pattern)、迭代器模式(Iterator Pattern)、中介者模式(Mediator Pattern)、备忘录模式(Memento Pattern)、观察者模式(Observer Pattern)、状态模式(State Pattern)、空对象模式(Null Object Pattern)、策略模式(Strategy Pattern)、模板模式(Template Pattern)、访问者模式(Visitor Pattern)
4 J2EE 模式这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 MVC 模式(MVC Pattern)、业务代表模式(Business Delegate Pattern)、组合实体模式(Composite Entity Pattern)、数据访问对象模式(Data Access Object Pattern)、前端控制器模式(Front Controller Pattern)、拦截过滤器模式(Intercepting Filter Pattern)、服务定位器模式(Service Locator Pattern)、传输对象模式(Transfer Object Pattern)

下面用一个图片来整体描述一下设计模式之间的关系:

design

设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

笔记:

  1. 开闭原则:实现热插拔,提高扩展性。

  2. 里氏代换原则:实现抽象的规范,实现子父类互相替换;

  3. 依赖倒转原则:针对接口编程,实现开闭原则的基础;

  4. 接口隔离原则:降低耦合度,接口单独设计,互相隔离;

  5. 迪米特法则,又称不知道原则:功能模块尽量独立;

  6. 合成复用原则:尽量使用聚合,组合,而不是继承;

Java就是要搞对象啊

  1. 创建型模式–>对象怎么来
  2. 结构型模式–>对象和谁有关
  3. 行为型模式–>对象与对象在干嘛
  4. J2EE 模式–>对象合起来要干嘛(表现层,文中表示层个人感觉用的不准确)java是面向对象的语言,所以要搞好对象,模式(套路)就是用来更加好的搞对象滴。

工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

介绍

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

何时使用:我们明确地计划不同条件下创建不同实例时。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

关键代码:创建过程在其子类执行。

应用实例:

  1. 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现
  2. Hibernate 换数据库只需换方言和驱动就可以。

优点:

  1. 一个调用者想创建一个对象,只要知道其名称就可以了。
  2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  3. 屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:

每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

使用场景:

  1. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  2. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  3. 设计一个连接服务器的框架,需要三个协议,”POP3”、”IMAP”、”HTTP”,可以把这三个作为产品类,共同实现一个接口。

注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

实现

我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory

FactoryPatternDemo,我们的演示类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。

design

步骤 1

创建一个接口。

Shape.java

1
2
3
4
5
package com.test;

public interface Shape {
void draw();
}

步骤 2

创建实现接口的实体类。

Rectangle.java

1
2
3
4
5
6
7
8
package com.test;
public class Rectangle implements Shape {

public void draw() {
System.out.println("Inside Square::draw() method.");
}

}

Circle.java

1
2
3
4
5
6
7
8
package com.test;
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}

Square.java

1
2
3
4
5
6
7
8
package com.test;
public class Square implements Shape {

@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}

步骤 3

创建一个工厂,生成基于给定信息的实体类的对象。

ShapeFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.test;

public class ShapeFactory {
public Shape getShape(String shapeType){
if (shapeType==null) {
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
}else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}

步骤 4

使用该工厂,通过传递类型信息来获取实体类的对象。

FactoryPatternDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.test;

public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
// 获取 CIRCLE 的对象,并调用它的 draw 方法
Shape shape1 = shapeFactory.getShape("CIRCLE");
// 调用 Circle 的 draw 方法
shape1.draw();
// 获取 rectangle 的对象,并调用它的 draw 方法
Shape shape2 = shapeFactory.getShape("RECTANGLE");
// 调用 rectangle 的 draw 方法
shape2.draw();
// 获取 square 的对象,并调用它的 draw 方法
Shape shape3 = shapeFactory.getShape("SQUARE");
// 调用 square 的 draw 方法
shape3.draw();
}
}

步骤 5

验证输出。

1
2
3
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.

笔记:

  1. 使用反射机制可以解决每次增加一个产品时,都需要增加一个对象实现工厂的缺点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ShapeFactory {
    public static Object getClass(Class<?extends Shape> clazz) {
    Object obj = null;

    try {
    obj = Class.forName(clazz.getName()).newInstance();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }

    return obj;
    }
    }

    使用的使用采用强制转换

    1
    2
    3
    4
    Rectangle rect = (Rectangle) ShapeFactory.getClass(Rectangle.class);
    rect.draw();
    Square square = (Square) ShapeFactory.getClass(Square.class);
    square.draw();

    这样就只需要一个对象实现工厂

  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ShapeFactory {
    public static <T> T getClass(Class<? extends T> clazz) {
    T obj = null;

    try {
    obj = (T) Class.forName(clazz.getName()).newInstance();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }

    return obj;
    }
    }

    省略类型强制转换,支持多态

    1
    2
    3
    4
    5
    Rectangle rect = ShapeFactory.getClass(Rectangle.class);
    rect.draw();

    Shape square = ShapeFactory.getClass(Square.class);
    square.draw();

抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

介绍

意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

主要解决:主要解决接口选择的问题。

何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

如何解决:在一个产品族里面,定义多个产品。

关键代码:在一个工厂里聚合多个同类产品。

应用实例:工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OO 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

使用场景:

  1. QQ 换皮肤,一整套一起换。
  2. 生成不同操作系统的程序。

注意事项:产品族难扩展,产品等级易扩展。

实现

我们将创建 ShapeColor 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory。接着定义工厂类 ShapeFactoryColorFactory,这两个工厂类都是扩展了 AbstractFactory。然后创建一个工厂创造器/生成器类 FactoryProducer

AbstractFactoryPatternDemo,我们的演示类使用 FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 ShapeCIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。同时它还向 AbstractFactory 传递颜色信息 ColorRED / GREEN / BLUE),以便获取它所需对象的类型。

design

步骤 1

为形状创建一个接口。

Shape.java

1
2
3
public interface Shape {
void draw();
}

步骤 2

创建实现接口的实体类。

Rectangle.java

1
2
3
4
5
6
7
public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}

Square.java

1
2
3
4
5
6
7
public class Square implements Shape {

@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}

Circle.java

1
2
3
4
5
6
7
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}

步骤 3

为颜色创建一个接口。

Color.java

1
2
3
public interface Color {
void fill();
}

步骤4

创建实现接口的实体类。

Red.java

1
2
3
4
5
6
7
public class Red implements Color {

@Override
public void fill() {
System.out.println("Inside Red::fill() method.");
}
}

Green.java

1
2
3
4
5
6
7
public class Green implements Color {

@Override
public void fill() {
System.out.println("Inside Green::fill() method.");
}
}

Blue.java

1
2
3
4
5
6
7
public class Blue implements Color {

@Override
public void fill() {
System.out.println("Inside Blue::fill() method.");
}
}

步骤 5

为 Color 和 Shape 对象创建抽象类来获取工厂。

AbstractFactory.java

1
2
3
4
public abstract class AbstractFactory {
abstract Color getColor(String color);
abstract Shape getShape(String shape);
}

步骤 6

创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象。

ShapeFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ShapeFactory extends AbstractFactory {

@Override
Color getColor(String color) {
return null;
}

@Override
Shape getShape(String shapeType) {
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}

ColorFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ColorFactory extends AbstractFactory {

@Override
Color getColor(String color) {
if (color == null) {
return null;
}
if (color.equalsIgnoreCase("RED")) {
return new Red();
} else if (color.equalsIgnoreCase("GREEN")) {
return new Green();
} else if (color.equalsIgnoreCase("BLUE")) {
return new Blue();
}
return null;
}

@Override
Shape getShape(String shapeType) {
return null;
}

}

步骤 7

创建一个工厂创造器/生成器类,通过传递形状或颜色信息来获取工厂。

FactoryProducer.java

1
2
3
4
5
6
7
8
9
10
public class FactoryProducer  {
public static AbstractFactory getFactory(String choice){
if (choice.equalsIgnoreCase("SHAPE")) {
return new ShapeFactory();
}else if (choice.equalsIgnoreCase("COLOR")) {
return new ColorFactory();
}
return null;
}
}

步骤 8

使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。

AbstractFactoryPatternDemo.java

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 class AbstractFactoryPatternDemo {
public static void main(String[] args) {

//获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");

//获取形状为 Circle 的对象
Shape shape1 = shapeFactory.getShape("CIRCLE");

//调用 Circle 的 draw 方法
shape1.draw();

//获取形状为 Rectangle 的对象
Shape shape2 = shapeFactory.getShape("RECTANGLE");

//调用 Rectangle 的 draw 方法
shape2.draw();

//获取形状为 Square 的对象
Shape shape3 = shapeFactory.getShape("SQUARE");

//调用 Square 的 draw 方法
shape3.draw();

//获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");

//获取颜色为 Red 的对象
Color color1 = colorFactory.getColor("RED");

//调用 Red 的 fill 方法
color1.fill();

//获取颜色为 Green 的对象
Color color2 = colorFactory.getColor("Green");

//调用 Green 的 fill 方法
color2.fill();

//获取颜色为 Blue 的对象
Color color3 = colorFactory.getColor("BLUE");

//调用 Blue 的 fill 方法
color3.fill();
}
}

步骤 9

验证输出。

1
2
3
4
5
6
Inside Circle::draw() method.
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Red::fill() method.
Inside Green::fill() method.
Inside Blue::fill() method.

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例:

  1. 一个党只能有一个主席。
  2. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  3. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 
2. 避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  1. 要求生产唯一序列号。
  2. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  3. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

实现

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。

design

步骤 1

创建一个 Singleton 类。

SingleObject.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SingleObject {

//创建 SingleObject 的一个对象
private static SingleObject instance = new SingleObject();

//让构造函数为 private,这样该类就不会被实例化
private SingleObject(){}

//获取唯一可用的对象
public static SingleObject getInstance(){
return instance;
}

public void showMessage(){
System.out.println("Hello World!");
}
}

关于对象的实例化 大家想到的通常是直接new,除了这个,还有些单实例模式,层次间调用等等

getInstance的使用:

在主函数开始时调用,返回一个实例化对象,此对象是static的,在内存中保留着它的引用,即内存中有一块区域专门用来存放静态方法和变量,可以直接使用,调用多次返回同一个对象。

getInstance 和 new的区别;
大部分类都可以用new,new就是通过生产一个新的实例对象,或者在栈上声明一个对象,每部分的调用都是用的一个新的对象

getInstance在单例模式(保证一个类仅有一个实例,并提供一个访问它的全局访问点)的类中常见,用来生成唯一的实例,getInstance往往是static的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> public abstract class TestInstance { 
> static int i = 1;
> public static TestInstance getInstance() {
> System.out.println(i++);
> return null;
> }
> }
> public class TestGetInstance {
> public static void main(String[] args) {
> TestInstance ins1 = TestInstance.getInstance();
> TestInstance ins2 = TestInstance.getInstance();
> }
> }
>

>

使用实例,在定义的新类当中加入getInstance()方法,返回值即为该类的一个对象,此方法常常应用在Java的单例模式当中

1
2
3
4
5
6
7
8
> public class OAuthConstant {  
> public static synchronized OAuthConstant getInstance(){
> if(instance == null)
> instance = new OAuthConstant();
> return instance;
> }
> }
>

>

//主函数当中使用此类的getInstance()函数,即可得到系统当前已经实例化的该类对象,若当前系统还没有实例化过这个类的对象,则调用此类的构造函数

//实例化一个对象,并返回

OAuthConstant.getInstance();

  1. 对象使用之前通过getInstance得到而不需要自己定义,用完之后不需要delete;
  2. new 一定要生成一个新对象,分配内存;getInstance() 则不一定要再次创建,它可以把一个已存在的引用给你使用,这在效能上优于new;
  3. new创建后只能当次使用,而getInstance()可以跨栈区域使用,或者远程跨区域使用。所以getInstance()通常是创建static静态实例方法的。
  4. 对于抽象类,是只能用getInstance()方法,是不能new出来的,

总结:

getInstance这个方法在单例模式用的甚多,为了避免对内存造成浪费,直到需要实例化该类的时候才将其实例化,所以用getInstance来获取该对象,至于其他时候,也就是为了简便而已,为了不让程序在实例化对象的时候,不用每次都用new关键字,索性提供一个instance方法,不必一执行这个类就初始化,这样做到不浪费系统资源!单例模式 可以防止 数据的冲突,节省内存空间。

步骤 2

从 singleton 类获取唯一的对象。

SingletonPatternDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SingletonPatternDemo {
public static void main(String[] args) {

//不合法的构造函数
//编译时错误:构造函数 SingleObject() 是不可见的
//SingleObject object = new SingleObject();

//获取唯一可用的对象
SingleObject object = SingleObject.getInstance();

//显示消息
object.showMessage();
}
}

步骤 3

验证输出。

1
Hello World!

单例模式的几种实现方式

单例模式的实现有多种方式,如下所示:

1、懒汉式,线程不安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

代码实例:

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton instance;
private Singleton (){}

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

接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

2、懒汉式,线程安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

代码实例:

1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

3、饿汉式

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

代码实例:

1
2
3
4
5
6
7
public class Singleton {  
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

4、双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {  
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

5、登记式/静态内部类

是否 Lazy 初始化:

是否多线程安全:

实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

代码实例:

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

6、枚举

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

代码实例:

1
2
3
4
5
public enum Singleton {  
INSTANCE;
public void whateverMethod() {
}
}

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

介绍

意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

主要解决:主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

应用实例:

  1. 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的”套餐”。
  2. JAVA 中的 StringBuilder。

优点:

  1. 建造者独立,易扩展。
  2. 便于控制细节风险。

缺点:

  1. 产品必须有共同点,范围有限制。
  2. 如内部变化复杂,会有很多的建造类。

使用场景:

  1. 需要生成的对象具有复杂的内部结构。
  2. 需要生成的对象内部属性本身相互依赖。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

实现

我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。

我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。

然后我们创建一个 Meal 类,带有 ItemArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilderBuilderPatternDemo,我们的演示类使用 MealBuilder 来创建一个 Meal

design

步骤 1

创建一个表示食物条目和食物包装的接口。

Item.java

1
2
3
4
5
public interface Item {
public String name();
public Packing packing();
public float price();
}

Packing.java

1
2
3
public interface Packing {
public String pack();
}

步骤 2

创建实现 Packing 接口的实体类。

Wrapper.java

1
2
3
4
5
6
7
public class Wrapper implements Packing {

@Override
public String pack() {
return "Wrapper";
}
}

Bottle.java

1
2
3
4
5
6
7
public class Bottle implements Packing {

@Override
public String pack() {
return "Bottle";
}
}

步骤 3

创建实现 Item 接口的抽象类,该类提供了默认的功能。

Burger.java

1
2
3
4
5
6
7
8
9
10
public abstract class Burger implements Item {

@Override
public Packing packing() {
return new Wrapper();
}

@Override
public abstract float price();
}

ColdDrink.java

1
2
3
4
5
6
7
8
9
10
public abstract class ColdDrink implements Item {

@Override
public Packing packing() {
return new Bottle();
}

@Override
public abstract float price();
}

步骤 4

创建扩展了 Burger 和 ColdDrink 的实体类。

VegBurger.java

1
2
3
4
5
6
7
8
9
10
11
12
public class VegBurger extends Burger {

@Override
public float price() {
return 25.0f;
}

@Override
public String name() {
return "Veg Burger";
}
}

ChickenBurger.java

1
2
3
4
5
6
7
8
9
10
11
12
public class ChickenBurger extends Burger {

@Override
public float price() {
return 50.5f;
}

@Override
public String name() {
return "Chicken Burger";
}
}

Coke.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Coke extends ColdDrink {

@Override
public float price() {
return 30.0f;
}

@Override
public String name() {
return "Coke";
}
}

Pepsi.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Pepsi extends ColdDrink {

@Override
public float price() {
return 35.0f;
}

@Override
public String name() {
return "Pepsi";
}
}

步骤 5

创建一个 Meal 类,带有上面定义的 Item 对象。

Meal.java

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
import java.util.ArrayList;
import java.util.List;

public class Meal {
private List<Item> items = new ArrayList<Item>();

public void addItem(Item item){
items.add(item);
}

public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}

public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}

步骤 6

创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。

MealBuilder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MealBuilder {

public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}

public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}

步骤 7

BuiderPatternDemo 使用 MealBuider 来演示建造者模式(Builder Pattern)。

BuilderPatternDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();

Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());

Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}

步骤 8

验证输出。

1
2
3
4
5
6
7
8
9
10
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0


Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5
Donate comment here
Title - Artist
0:00