天天看点

设计模式设计模式

设计模式

设计模式的目的

在编写软件过程中,面临着来自耦合性,内举行以及可维护性,可扩展性,重用性,灵活性等多方面的问题,而设计模式就是来解决这些问题的,使程序具有更好的:

  1. 代码重用性(相同功能的代码,不用多次编写)
  2. 可读性(编程规范性,便于其他程序员的阅读和理解)
  3. 可扩展性(当需要增加新的功能时,非常的方便,成为可维护)
  4. 可靠性(当我们新增功能后,对原来的功能没有影响)
  5. 使程序呈现高内聚,低耦合的特性

设计模式的七大原则

设计模式原则其实就是在编写的时候应当遵守的原则,也就是各种设计模式的基础(即设计模式为什么这样设计的依据)

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒转原则
  4. 里氏替换原则
  5. 开闭原则ocp
  6. 迪米特原则
  7. 合成复用原则

单一职责原则

理解:一个类只负责一项职责,提高类的可读性、可维护性。

实例:以交通工具为例

package cn.zhku;

public class SingleResponsibility1 {
    public static void main(String[] args) {
        RoadVehicle roadVehicle = new RoadVehicle();
        AirVehicle airVehicle = new AirVehicle();
        WaterVehicle waterVehicle = new WaterVehicle();
        roadVehicle.run("摩托车");
        airVehicle.run("飞机");
        waterVehicle.run("船");
    }
}
//遵守了单一职责原则,但是这样做的改动很大,要将类分解,而且还要修改客户端代码
//改进:直接修改Vehicle类,改动的代码会比较少
class RoadVehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在公路上运行");
    }
}
class AirVehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在空中上运行");
    }
}
class WaterVehicle{
    public void run(String vehicle){
        System.out.println(vehicle+"在水上运行");
    }
}
           
package cn.zhku;

public class SingleResponsibility2 {
    public static void main(String[] args) {
        Vehicle2 vehicle2 = new Vehicle2();
        vehicle2.run("汽车");
        vehicle2.runAir("飞机");
        vehicle2.runWater("轮船");
    }
}

/**
 * 第二种方式:
 *      这种修改方法没有对原来的类做大的修改,只是增加了方法
 *      这里虽然没有在类这个级别上遵守单一职责原则,但是在方法这个级别上任然遵守了单一原则
 */
class Vehicle2{
    public void run(String vehicle){
        System.out.println(vehicle+"在公路上运行");
    }
    public void runAir(String vehicle){
        System.out.println(vehicle+"在天空运行");
    }
    public void runWater(String vehicle){
        System.out.println(vehicle+"在水中运行");
    }
}
           

接口隔离原则

理解:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

实例:有一个接口,里面含有5个方法,要求A类通过接口依赖B类,但是只用到1,2,3方法;C类通过接口依赖D,但是只用到1,4,5方法。

原始版本:

package cn.zhku;
//要求A类通过接口依赖B,但是只用到1,2,3方法
//C类通过接口依赖D,但是只用到1,4,5方法,明显这里的接口不满足接口隔离原则
public class Segregation1 {
    public static void main(String[] args) {

    }
}
interface Interface1{
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}

class B implements Interface1{
    @Override
    public void operation1() {
        System.out.println("B中实现了operation1");
    }
    @Override
    public void operation2() {
        System.out.println("B中实现了operation2");
    }
    @Override
    public void operation3() {
        System.out.println("B中实现了operation3");
    }
    @Override
    public void operation4() {
        System.out.println("B中实现了operation4");
    }
    @Override
    public void operation5() {
        System.out.println("B中实现了operation5");
    }
}

class D implements Interface1{
    @Override
    public void operation1() {
        System.out.println("D中实现了operation1");
    }
    @Override
    public void operation2() {
        System.out.println("D中实现了operation2");
    }
    @Override
    public void operation3() {
        System.out.println("D中实现了operation3");
    }
    @Override
    public void operation4() {
        System.out.println("D中实现了operation4");
    }
    @Override
    public void operation5() {
        System.out.println("D中实现了operation5");
    }
}

class A{//A类通过接口Interface1依赖使用B类,但是只会用到1,2,3方法
    public void depend1(Interface1 i){
        i.operation1();
    }
    public void depend2(Interface1 i){
        i.operation2();
    }public void depend3(Interface1 i){
        i.operation3();
    }
}

class C {//A类通过接口Interface1依赖使用B类,但是只会用到1,2,3方法

    public void depend1(Interface1 i) {
        i.operation1();
    }

    public void depend4(Interface1 i) {
        i.operation4();
    }

    public void depend5(Interface1 i) {
        i.operation5();
    }
}

           

问题所在:接口中有5个方法,但是A类在使用B的时候只用到了其中的三个方法,所以会导致接口过剩的情况。

解决:将接口拆分成三个接口

package cn.zhku;

public class Segregation2 {
    public static void main(String[] args) {
        A1 a1 = new A1();
        a1.depend1(new B1());//A1类通过接口依赖B1类
        a1.depend2(new B1());
        a1.depend3(new B1());

        C1 c1 = new C1();
        c1.depend1(new D1());
        c1.depend4(new D1());
        c1.depend5(new D1());
    }
}
interface InterfaceA{
    void operation1();
}
interface InterfaceB{
    void operation2();
    void operation3();
}
interface InterfaceC{
    void operation4();
    void operation5();
}

class B1 implements InterfaceA,InterfaceB{
    @Override
    public void operation1() {
        System.out.println("B中实现了operation1");
    }
    @Override
    public void operation2() {
        System.out.println("B中实现了operation2");
    }
    @Override
    public void operation3() {
        System.out.println("B中实现了operation3");
    }
}
class D1 implements InterfaceA,InterfaceC{
    @Override
    public void operation1() {
        System.out.println("D中实现了operation1");
    }

    @Override
    public void operation4() {
        System.out.println("D中实现了operation4");
    }

    @Override
    public void operation5() {
        System.out.println("D中实现了operation5");
    }
}
class A1{//A类通过接口Interface1依赖使用B类,但是只会用到1,2,3方法
    public void depend1(InterfaceA i){
        i.operation1();
    }
    public void depend2(InterfaceB i){
        i.operation2();
    }
    public void depend3(InterfaceB i){
        i.operation3();
    }
}

class C1 {//A类通过接口Interface1依赖使用B类,但是只会用到1,2,3方法

    public void depend1(InterfaceA i) {
        i.operation1();
    }

    public void depend4(InterfaceC i) {
        i.operation4();
    }

    public void depend5(InterfaceC i) {
        i.operation5();
    }
}
           

依赖倒转原则

理解:高层模块不应该依赖底层模块,二者都应该依赖器抽象(接口或抽象类)

中心思想:面向接口编程

案例:完成Person接收消息的功能

package cn.zhku;

public class DependencyInversion {
    public static void main(String[] args) {

    }
}
class Email{
    public String getInfo(){
        return "电子邮件信息:hello";
    }
}

//完成Person接收消息的功能

//方式1
//分析:如果获取的对象是别的,比如微信,则新增类,同时Person也要新增相应的接收方法
//解决:引入一个抽象的接口IReceiver表示接受者,这样Person与这个接口发生依赖
//因为Email,WeiXin等等都属于接受者的范围,它们各自实现IReceiver接口就行了,这样就符合依赖倒转原则
class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}
interface IReceiver{

}
           

解决:

package cn.zhku.improve;

public class DependencyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
        person.receive(new WeiXin());
    }
}

class Email implements IReceiver{
    @Override
    public String getInfo(){
        return "Email电子邮件信息:hello";
    }
}
class WeiXin implements IReceiver{

    @Override
    public String getInfo() {
        return "微信电子邮件信息:hello";
    }
}
//完成Person接收消息的功能

//定义接口
interface IReceiver{
    public String getInfo();
}
//方式二
class Person{
    public void receive(IReceiver receiver){
        System.out.println(receiver.getInfo());
    }
}

           

注意:低层模块尽量都要有抽象类或接口,程序稳定性会更好;变量的声明类型尽量是抽象类或接口,这样变量的引用和实际对象间就存在一个缓冲层,有利于程序扩展和优化。

里式替换原则

理解:继承实际上让两个类的耦合性增强了,在适当的情况下可以通过聚合,组合,依赖来解决问题,在子类中尽量不要重写父类的方法。

案例:

package cn.zhku;

public class Liskov {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));
        System.out.println("1-8=" + a.func1(1, 8));

        System.out.println("-----------------------");
        B b = new B();
        System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出11-3,但是func1被重写了,所以结果不符合
        System.out.println("1-8=" + b.func1(1, 8));// 1-8
        System.out.println("11+3+9=" + b.func2(11, 3));



    }

}

// A类
class A {
    // 返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends A {
    //这里,重写了A类的方法, 可能是无意识
    public int func1(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }
}
           

问题:B类继承了A类,但是B类无意中重写了A类的方法,所以在后面调用B类的方法时结果会与原本不符合。

解决:让原来的父类和子类都继承于一个更通俗的父类,原有的继承关系去掉。

package cn.zhku.improve;

public class Liskov {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));
        System.out.println("1-8=" + a.func1(1, 8));

        System.out.println("-----------------------");
        B b = new B();
        //因为B类不再继承A类,因此调用者,不会再func1是求减法
        //调用完成的功能就会很明确
        System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出11+3
        System.out.println("1+8=" + b.func1(1, 8));// 1+8
        System.out.println("11+3+9=" + b.func2(11, 3));


        //使用组合仍然可以使用到A类相关方法
        System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出11-3


    }

}

//创建一个更加基础的基类
class Base {
    //把更加基础的方法和成员写到Base类
}

// A类
class A extends Base {
    // 返回两个数的差
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}

// B类继承了A
// 增加了一个新功能:完成两个数相加,然后和9求和
class B extends Base {
    //如果B需要使用A类的方法,使用组合关系
    private A Aa = new A();

    //这里,重写了A类的方法, 可能是无意识
    public int func1(int a, int b) {
        return a + b;
    }

    public int func2(int a, int b) {
        return func1(a, b) + 9;
    }

    //我们仍然想使用A的方法
    public int func3(int a, int b) {
        return this.Aa.func1(a, b);
    }
}

           

开闭原则

理解:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

实例:

package cn.zhku;

public class Ocp {

	public static void main(String[] args) {
		//使用看看存在的问题
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
	}

}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
	//接收Shape对象,然后根据type,来绘制不同的图形
	public void drawShape(Shape s) {
		if (s.m_type == 1)
			drawRectangle(s);
		else if (s.m_type == 2)
			drawCircle(s);
		else if (s.m_type == 3)
			drawTriangle(s);
	}

	//绘制矩形
	public void drawRectangle(Shape r) {
		System.out.println(" 绘制矩形 ");
	}

	//绘制圆形
	public void drawCircle(Shape r) {
		System.out.println(" 绘制圆形 ");
	}

	//绘制三角形
	public void drawTriangle(Shape r) {
		System.out.println(" 绘制三角形 ");
	}
}

//Shape类,基类
class Shape {
	int m_type;
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}
}

//新增画三角形
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}
}

           

问题分析:在新增一个画图功能的时候,需要在GraphicEditor类中作else if判断,没有遵循开闭原则,代码在管理起来变得复杂。

解决:把创建Shape类做成抽象类,并提供一个抽象方法draw,让子类去实现。当有新功能增加时只需要让新的图形类去继承Shape,并且实现draw方法,那么适用方GraphicEditor类就不用修改代码了,里面直接调用draw方法就行了。

package cn.zhku.improve;

public class Ocp {

	public static void main(String[] args) {
		//使用看看存在的问题
		GraphicEditor graphicEditor = new GraphicEditor();
		graphicEditor.drawShape(new Rectangle());
		graphicEditor.drawShape(new Circle());
		graphicEditor.drawShape(new Triangle());
		graphicEditor.drawShape(new Other());
	}

}

//这是一个用于绘图的类 [使用方]
class GraphicEditor {
	//接收Shape对象,然后根据type,来绘制不同的图形
	public void drawShape(Shape s) {
		s.draw();
	}

}

//Shape类,基类
abstract class Shape {
	int m_type;
	public abstract void draw();//抽象方法
}

class Rectangle extends Shape {
	Rectangle() {
		super.m_type = 1;
	}

	@Override
	public void draw() {
		System.out.println("绘制矩形");
	}
}

class Circle extends Shape {
	Circle() {
		super.m_type = 2;
	}

	@Override
	public void draw() {
		System.out.println("绘制圆形");
	}
}

//新增画三角形
class Triangle extends Shape {
	Triangle() {
		super.m_type = 3;
	}

	@Override
	public void draw() {
		System.out.println("绘制三角形");
	}
}
//新增图形
class Other extends Shape{
	Other(){
		super.m_type = 4;
	}
	@Override
	public void draw() {
		System.out.println("绘制其他图形");
	}
}
           

迪米特法则

理解:最少知道原则,即一个类对自己依赖的类知道的越少越好,也就是说对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息。

直接朋友:每个对象都会与其他对象有耦合关系,只是两个对象之间有耦合关系,其中我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友。

案例:

import java.util.ArrayList;
import java.util.List;

//客户端
public class Demeter1 {

    public static void main(String[] args) {
        //创建了一个 SchoolManager 对象
        SchoolManager schoolManager = new SchoolManager();
        //输出学院的员工id 和  学校总部的员工信息
        schoolManager.printAllEmployee(new CollegeManager());

    }

}


//学校总部员工类
class Employee {
    private String id;

    public void setId(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }
}


//学院的员工类
class CollegeEmployee {
    private String id;

    public void setId(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }
}


//管理学院员工的管理类
class CollegeManager {
    //返回学院的所有员工
    public List<CollegeEmployee> getAllEmployee() {
        List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
        for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("学院员工id= " + i);
            list.add(emp);
        }
        return list;
    }
}

//学校管理类

//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则
class SchoolManager {
    //返回学校总部的员工
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<Employee>();

        for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
            Employee emp = new Employee();
            emp.setId("学校总部员工id= " + i);
            list.add(emp);
        }
        return list;
    }

    //该方法完成输出学校总部和学院员工信息(id)
    void printAllEmployee(CollegeManager sub) {

        //分析问题
        //1. 这里的 CollegeEmployee 不是  SchoolManager的直接朋友
        //2. CollegeEmployee 是以局部变量方式出现在 SchoolManager
        //3. 违反了 迪米特法则

        //获取到学院员工
        List<CollegeEmployee> list1 = sub.getAllEmployee();
        System.out.println("------------学院员工------------");
        for (CollegeEmployee e : list1) {
            System.out.println(e.getId());
        }
        //获取到学校总部员工
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("------------学校总部员工------------");
        for (Employee e : list2) {
            System.out.println(e.getId());
        }
    }
}

           

解决:

import java.util.ArrayList;
import java.util.List;

//客户端
public class Demeter1 {

	public static void main(String[] args) {
		System.out.println("~~~使用迪米特法则的改进~~~");
		//创建了一个 SchoolManager 对象
		SchoolManager schoolManager = new SchoolManager();
		//输出学院的员工id 和  学校总部的员工信息
		schoolManager.printAllEmployee(new CollegeManager());

	}

}


//学校总部员工类
class Employee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//学院的员工类
class CollegeEmployee {
	private String id;

	public void setId(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}
}


//管理学院员工的管理类
class CollegeManager {
	//返回学院的所有员工
	public List<CollegeEmployee> getAllEmployee() {
		List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
		for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到 list
			CollegeEmployee emp = new CollegeEmployee();
			emp.setId("学院员工id= " + i);
			list.add(emp);
		}
		return list;
	}
	
	//输出学院员工的信息
	public void printEmployee() {
		//获取到学院员工
		List<CollegeEmployee> list1 = getAllEmployee();
		System.out.println("------------学院员工------------");
		for (CollegeEmployee e : list1) {
			System.out.println(e.getId());
		}
	}
}

//学校管理类

//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager
//CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则 
class SchoolManager {
	//返回学校总部的员工
	public List<Employee> getAllEmployee() {
		List<Employee> list = new ArrayList<Employee>();
		
		for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到 list
			Employee emp = new Employee();
			emp.setId("学校总部员工id= " + i);
			list.add(emp);
		}
		return list;
	}

	//该方法完成输出学校总部和学院员工信息(id)
	void printAllEmployee(CollegeManager sub) {
		
		//分析问题
		//1. 将输出学院的员工方法,封装到CollegeManager
		sub.printEmployee();
	
		//获取到学校总部员工
		List<Employee> list2 = this.getAllEmployee();
		System.out.println("------------学校总部员工------------");
		for (Employee e : list2) {
			System.out.println(e.getId());
		}
	}
}
           

合成复用原则

理解:尽量使用合成/聚合的方式,而不是使用继承。

设计模式三种类型

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

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

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

创建者模式-单例模式

饿汉式(静态常量)

步骤:

  1. 构造器私有化(防止 new)
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法(getInstance)

代码:

package type1;

public class SingletonTest1 {
    public static void main(String[] args) {
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("两次创建的对象是否相同:"+(instance1==instance2));

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//饿汉式(静态变量)
class Singleton{
    //1、构造器私有化
    private Singleton(){

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

    //3、对外提供一个公有的静态方法
    public static Singleton getInstance(){
        return instance;
    }
}
           

优点:写法比较简单,在类装载的时候完成了实例化,避免了线程同步的问题。

缺点:在类装载的时候就完成实例化,没有打到懒加载的效果,如果从始至终都没有使用过这个实力,则会造成内存的浪费。

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

饿汉式(静态代码块)

代码:

package type2;

public class SingletonTest2 {
    public static void main(String[] args) {
        //测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("两次创建的对象是否相同:"+(instance1==instance2));

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//饿汉式(静态变量)
class Singleton{
    //1、构造器私有化
    private Singleton(){

    }
    //2、在本类内部创建对象实例
    private static Singleton instance;

    static {//在静态代码块中,创建单例对象
        instance = new Singleton();
    }
    //3、对外提供一个公有的静态方法
    public static Singleton getInstance(){
        return instance;
    }
}
           
  1. 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块 中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优 缺点和上面是一样的。
  2. 结论:这种单例模式可用,但是可能造成内存浪费

懒汉式(线程不安全)

步骤:

  1. 构造器私有化
  2. 定义一个类成员变量
  3. 提供一个静态的公有方法,当使用到该方法的时候才去创建instance对象

    代码:

package type3;

public class SingletonTest3 {
    public static void main(String[] args) {
        System.out.println("懒汉式(线程不安全)");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("两次创建的对象是否相同:"+(instance1==instance2));

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton{
    private static Singleton instance;

    private Singleton(){

    }

    //提供一个静态的公有方法,当使用到该方法时,才去创建instance
    //懒汉式,在使用的时候才开始创建对象
    public static Singleton getInstance(){
        //判断有没有对象,如果没有就创建,有则返回
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
           

优缺点:

  1. 起到了懒加载的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及 往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以 在多线程环境下不可使用这种方式 。
  3. 结论:在实际开发中,不要使用这种方式。

懒汉式(线程安全,同步方法)

步骤:在上一种的方式中对方法加入了同步代码块。

//提供一个静态的公有方法,加入了同步处理代码synchronized
//懒汉式,在使用的时候才开始创建对象
public static synchronized Singleton getInstance(){
    //判断有没有对象,如果没有就创建,有则返回
    if (instance == null){
        instance = new Singleton();
    }
    return instance;
}
           

优缺点:

  1. 解决了线程不安全问题。
  2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行 同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接return就行了。方法进行同步效率太低 。
  3. 结论:在实际开发中,不推荐使用这种方式。

懒汉式(线程安全,同步代码块)

代码:

public static Singleton getInstance(){
    //判断有没有对象,如果没有就创建,有则返回
    if (instance == null){
        synchronized (Singleton.class){
            instance = new Singleton();   
        }
    }
    return instance;
}
           
  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低, 改为同步产生实例化的的代码块。
  2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一 致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行, 另一个线程也通过了这个判断语句,这时便会产生多个实例 。
  3. 结论:在实际开发中,不能使用这种方式。

双重检查

代码:

package type6;

public class SingletonTest6 {
    public static void main(String[] args) {
        System.out.println("双重检查");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("两次创建的对象是否相同:"+(instance1==instance2));

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//双重检查
class Singleton{
    //加volatile关键字
    private static volatile Singleton instance;

    private Singleton(){

    }

    public static Singleton getInstance(){
        //提供一个公有的静态方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
        //同时保证了效率,推荐使用这种方式
        if (instance == null){
            synchronized ((Singleton.class)){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
           

优缺点:

  1. 双重检查概念是多线程开发中常使用到的,如代码中所示,我们进行了两 次if (singleton == null)检查,这样就可以保证线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null), 直接return实例化对象,也避免的反复进行方法同步。
  3. 线程安全;延迟加载;效率较高 。
  4. 结论:在实际开发中,推荐使用这种单例设计模式。

静态内部类

代码:

package type7;

public class SingletonTest7 {
    public static void main(String[] args) {
        System.out.println("静态内部类实现单例模式");
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("两次创建的对象是否相同:"+(instance1==instance2));

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

//静态内部类实现单例模式,推荐使用
class Singleton{
    //加volatile关键字
    private static volatile Singleton instance;

    private Singleton(){

    }
    //静态内部类一开始不会被实例化
    //写一个静态内部类,该类中有一个静态的属性,该类在一开始Singleton类被装载时并不会被立即实例化
    private static class SingleInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个静态的公有方法,直接返回SingleInstance.INSTANCE
    public static synchronized Singleton getInstance(){
        //返回SingleInstance静态内部类,而静态内部类在装载的时候是线程安全的
        return SingleInstance.INSTANCE;
    }
}
           

优缺点:

  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化 时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高 。
  5. 推荐使用

枚举

代码:

package type8;

public class SingletonTest8 {
    public static void main(String[] args) {
        SingleTon singleTon1 = SingleTon.INSTANCE;
        SingleTon singleTon2 = SingleTon.INSTANCE;
        System.out.println(singleTon1==singleTon2);

        System.out.println(singleTon1.hashCode());
        System.out.println(singleTon2.hashCode());
    }
}
//使用枚举可以实现单例,推荐使用
enum SingleTon{
    INSTANCE;//属性
    public void sayOK(){
        System.out.println("OK");
    }
}
           

优缺点:

  1. 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而 且还能防止反序列化重新创建新的对象。
  2. 推荐使用。

应用

在JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)

单例模式注意事项和细节说明

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需 要频繁创建销毁的对象,使用单例模式可以提高系统性能 。
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使 用new。
  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等)

创建者模式-工厂模式

简单工厂模式

理解:

  1. 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一 个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族 中最简单实用的模式 。
  2. 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行 为(代码)。

案例:

抽象类Pizza类

//将Pizza类做成抽象
public abstract class Pizza {
    protected String name; //名字

    //准备原材料, 不同的披萨不一样,因此,我们做成抽象方法
    public abstract void prepare();


    public void bake() {
        System.out.println(name + " baking;");
    }

    public void cut() {
        System.out.println(name + " cutting;");
    }

    //打包
    public void box() {
        System.out.println(name + " boxing;");
    }

    public void setName(String name) {
        this.name = name;
    }
}
           

三个Pizza类的子类

public class CheesePizza extends Pizza {

   @Override
   public void prepare() {
      // TODO Auto-generated method stub
      System.out.println(" 给制作奶酪披萨 准备原材料 ");
   }

}
public class GreekPizza extends Pizza {

	@Override
	public void prepare() {
		// TODO Auto-generated method stub
		System.out.println(" 给希腊披萨 准备原材料 ");
	}

}
public class PepperPizza extends Pizza {

	@Override
	public void prepare() {
		// TODO Auto-generated method stub
		System.out.println(" 给胡椒披萨准备原材料 ");
	}

}
           

模拟去披萨店买pizza的场景,客户需要告知店家要哪种pizza,然后店家开始按步骤制作。

店家代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import simplefactory.pizza.Pizza;

public class OrderPizza2 {

   Pizza pizza = null;
   String orderType = "";//pizza类型
   // 构造器
   public OrderPizza2() {
      
      do {
         orderType = getType();
         pizza = SimpleFactory.createPizza2(orderType);

         // 输出pizza
         if (pizza != null) { // 订购成功
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
         } else {
            System.out.println(" 订购披萨失败 ");
            break;
         }
      } while (true);
   }

   // 写一个方法,可以获取客户希望订购的披萨种类
   private String getType() {
      try {
         BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
         System.out.println("input pizza 种类:");
         String str = strin.readLine();
         return str;
      } catch (IOException e) {
         e.printStackTrace();
         return "";
      }
   }
}
           

而店家是把客户需要的pizza类型传给工厂,然后就得到一个pizza对象,再进行制作。这里的工厂类起到的作用是给店家传一个pizza的对象,它根据客户的要求去帮店家new一个pizza对象出来。

工厂代码:

import simplefactory.pizza.CheesePizza;
import simplefactory.pizza.GreekPizza;
import simplefactory.pizza.PepperPizza;
import simplefactory.pizza.Pizza;

//简单工厂类
public class SimpleFactory {
    //简单工厂模式 也叫 静态工厂模式
    public static Pizza createPizza2(String orderType) {

        Pizza pizza = null;

        System.out.println("使用简单工厂模式2");
        if (orderType.equals("greek")) {
            pizza = new GreekPizza();
            pizza.setName(" 希腊披萨 ");
        } else if (orderType.equals("cheese")) {
            pizza = new CheesePizza();
            pizza.setName(" 奶酪披萨 ");
        } else if (orderType.equals("pepper")) {
            pizza = new PepperPizza();
            pizza.setName("胡椒披萨");
        }

        return pizza;
    }

}
           

工厂方法模式

理解:

  1. 将披萨项目的实例化功能抽象成抽象方法,在不同的口味点 餐子类中具体实现。
  2. 定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方 法模式将对象的实例化推迟到子类。

案例::客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪 pizza、北京的胡椒pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza。

代码:

//将Pizza类做成抽象
public abstract class Pizza {
    protected String name; //名字

    //准备原材料, 不同的披萨不一样,因此,我们做成抽象方法
    public abstract void prepare();


    public void bake() {
        System.out.println(name + " baking;");
    }

    public void cut() {
        System.out.println(name + " cutting;");
    }

    //打包
    public void box() {
        System.out.println(name + " boxing;");
    }

    public void setName(String name) {
        this.name = name;
    }
}
           
public class BJCheesePizza extends Pizza{
    @Override
    public void prepare() {
        setName("北京奶酪pizza");
        System.out.println("北京的奶酪pizza准备原料");
    }
}
public class BJPepperPizza extends Pizza{
    @Override
    public void prepare() {
        setName("北京胡椒pizza");
        System.out.println("北京的胡椒pizza准备原料");
    }
}
public class LDCheesePizza extends Pizza{
    @Override
    public void prepare() {
        setName("伦敦奶酪pizza");
        System.out.println("伦敦的奶酪pizza准备原料");
    }
}
public class LDPepperPizza extends Pizza{
    @Override
    public void prepare() {
        setName("伦敦胡椒pizza");
        System.out.println("伦敦的胡椒pizza准备原料");
    }
}

           

店家类定义一个抽象方法createPizza,让工厂子类去实现

public abstract class OrderPizza {

   //定义一个抽象方法,createPizza , 让各个工厂子类自己实现
   abstract Pizza createPizza(String orderType);
   
   // 构造器
   public OrderPizza() {
      Pizza pizza = null;
      String orderType; // 订购披萨的类型
      do {
         orderType = getType();
         pizza = createPizza(orderType); //抽象方法,由工厂子类完成
         //输出pizza 制作过程
         pizza.prepare();
         pizza.bake();
         pizza.cut();
         pizza.box();
         
      } while (true);
   }

   

   // 写一个方法,可以获取客户希望订购的披萨种类
   private String getType() {
      try {
         BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
         System.out.println("input pizza 种类:");
         String str = strin.readLine();
         return str;
      } catch (IOException e) {
         e.printStackTrace();
         return "";
      }
   }

}
           
public class BJOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new BJCheesePizza();
        }else if (orderType.equals("pepper")){
            pizza = new BJPepperPizza();
        }
        return pizza;
    }
}
           
public class LDOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if (orderType.equals("cheess")){
            pizza = new LDCheesePizza();
        }else if (orderType.equals("pepper")){
            pizza = new LDPepperPizza();
        }
        return pizza;
    }
}
           

抽象工厂模式

理解:

  1. 定义了一个interface用于创建相关或有依赖关系的对象簇,而无需 指明具体的类。
  2. 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
  3. 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以 根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。

抽象工厂接口代码:

//一个抽象工厂模式的抽象层(接口)
public interface AbstractFactory {
    //让下面的工厂子类去具体实现
    public Pizza createPizza(String orderType);
}
           

具体的工厂类:

public class BJFactory implements AbstractFactory{
    @Override
    public Pizza createPizza(String orderType) {
        System.out.println("使用的是抽象工厂模式");
        Pizza pizza = null;
        if (orderType.equals("pepper")){
            pizza = new BJPepperPizza();
        }else if (orderType.equals("cheese")){
            pizza = new BJCheesePizza();
        }
        return pizza;
    }
}
           
public class LDFactory implements AbstractFactory{
    @Override
    public Pizza createPizza(String orderType) {
        System.out.println("使用的是抽象工厂模式");
        Pizza pizza = null;
        if (orderType.equals("pepper")){
            pizza = new LDPepperPizza();
        }else if (orderType.equals("cheese")){
            pizza = new LDCheesePizza();
        }
        return pizza;
    }
}
           

订购制作:

public class OrderPizza {
    AbstractFactory factory;

    //构造器,在调用的时候需要指明要用哪一家工厂(抽象工厂的实现类)
    public OrderPizza(AbstractFactory abstractFactory){
        setAbstractFactory(abstractFactory);
    }

    public void setAbstractFactory(AbstractFactory factory){
        Pizza pizza = null;
        String orderType = "";//用户输入
        this.factory = factory;

        do {
            orderType = getType();
            //factory可能是BJ工厂子类,也可以是LD工厂子类
            pizza = factory.createPizza(orderType);
            if (pizza!=null){
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else {
                System.out.println("订购失败");
                break;
            }
        }while (true);
    }



    // 写一个方法,可以获取客户希望订购的披萨种类
    private String getType() {
        try {
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza 种类:");
            String str = strin.readLine();
            return str;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}
           

在JDK中的源码应用

在Calendar类中应用了简单工厂模式

小结

  1. 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的 依赖关系的解耦。从而提高项目的扩展和维护性。
  2. 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)

注意事项

  1. 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法 中,并返回。有的书上说,变量不要直接持有具体类的引用。
  2. 不要让类继承具体类,而是继承抽象类或者是实现interface(接口) 。
  3. 不要覆盖基类中已经实现的方法。