• JAVA设计模式


    一、对象之间的关系

    1. 综述

    在Java中对象与对象的关系总体分为四类,分别是:依赖、关联、聚合和组合。

    依赖关系:是类和类之间的联接。依赖关系表示一个类依赖于另外一个类的定义,一般而言,依赖关系在Java语言中体现为局域变量、方法的形参,或者对静态方法的调用。

    关联关系:是类和类之间的联接,他使一个类知道另外一个类的属性和方法。关联可以是双向的,也可以是单向的。在Java语言中,关联关系一般使用成员变量来实现。

    聚合关系:是关联关系的一种,是强的关联关系。聚合是整体和个体之间的关系。

    组合关系:是关联关系的一种,是比聚合关系强的关系。

    2. 依赖关系

    个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。

    class A{
    	public void fun_A(){
    		...
    	}
    }
    
    class B{
    	public void fun1(A a){//使用参数方式发生与A的依赖关系,fun1方法依赖于A
    		a.fun_A();
    	}
    	
    	public void fun2(){//使用创建对象的方式与A发生依赖关系,fun2方法依赖于A
    		A a = new A();
    		a.fun_A();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3. 关联关系

    表现在代码上,就是一个类包含另一个类的实例,通常表现为被关联类以类属性的形式出现在关联类的类定义中,也可以表现为关联类引用了一个类型为被关联类的全局变量。关联可以使单向的,也可以使双向的。

    依赖和关联的区别在于依赖是使用,关联是拥有。

    class A{
    	public void fun_A(){
    	}
    
    }
    class B{
    	private A a ;
    	public B(A a){
    		this.a =a ;
    	}
    	public void work(){
    		a.fun_A() ;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4. 聚合关系

    它是一种强关联关系(has-a),聚合关系是整体和个体/部分之间的关系,一种单项关系。
    与关联关系的区别:关联关系的两个类处于同一个层次上,而聚合关系的两个类处于不同的层次上,一个是整体,一个是个体/部分

    class A{
    }
    
    class B{
    	private A a ;//与上面关联的代码不同之处在于使用A对象构造B,也就是说B是A的不可缺少的一部分
    	public B(A a){
    		this.a =a ;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5. 组合关系

    组合也是关联关系的一种(is-a),但它是比聚合关系更强的关系,也是整体/部分之间的关系,但是生命周期相同。
    聚合和组合关系的区别:
    1、依赖性区别
    聚合中的两种类(或实体)是可以单独存在的,不会相互影响;被关联的一方可以独立于关联一方,依赖性不强。
    相反,组合中的两个实体(或者类)是高度依赖于彼此的,它们之间会相互影响。
    2、生命周期的不同
    在聚合关系中的两个类(或实体)的生命周期是不同步;
    但在组合关系中的两个类(或实体)的生命周期是同步的。
    3、关联强度的不同
    聚合关联程度(耦合性)比组合要弱;
    在各种对象关系中,关系的强弱顺序为:泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖。
    4、关系类型的区别
    聚合代表了has-a关系,一种单向关系;组合代表了part-of关系。

    class A{
    ...
    }
    
    class B{
    	private A a;
    	public B(){
    		a= newA() ; //强调了生命周期相同
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    二、七大原则

    1. 单一职责原则

    在软件系统中,一个类只负责一个功能领域中的相应职责

    好处

    1. 类的复杂性降低,实现什么职责都有清晰明确的定义
    2. 可读性提高,复杂性降低,那当然可读性提高了
    3. 可维护性提高,那当然了,可读性提高,那当然更容易维护了
    4. 变更引起的风险降低,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大帮助

    注意:
    单一职责的应用应该要有一个合理的限度,并不是越单一越好。如果类的功能越单一,那么就会产生越多的类,类的数量越多就会造成整个系统的结构越复杂。所以我们在设计系统的时候需要找到一个合理的平衡点。

    2. 开放封闭原则

    一个软件实体应当对扩展开放,对修改关闭。在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。

    好处:

    1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    2. 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何的修改

    样例
    一个计算器的例子:

    //计算器类,可以根据输入的参数和操作进行一定运算
    public class Cal{
        public double oper( String oper , double num1, double num2){
            double rst = 0;
            if(oper.equals("+")){
                Add add = new Add();
                rst = add.cal(num1,num2) ;
            }else if(oper.equals("-")){
                sub sub = new Sub();
                rst = sub.cal( num1, num2);
            }
            return rst;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用开闭原则对其进行修改:

    //定义一个计算接口
    public interface Cal {
        public double cal(double num1,double num2);
    }
    
    //加法实现类
    public class Add implements Cal {
        @Override
        public double cal(double num1, double num2) {
            return num1+num2;
        }
    }
    
    //减法实现类
    public class Sub implements Cal {
        @Override
        public double cal(double num1, double num2) {
            return num1-num2;
        }
    }
    
    //工厂类
    public class Factory {
        public static Cal createInstance(String oper){
            Cal cal = null;
            switch (oper){
                case "+":
                    cal = new Add();
                    break;
                case "-":
                    cal = new Sub();
                    break;
            }
            return cal;
        }
    }
    
    public class MainTest {
        public static void main(String[] args) {
            //收集数据
            Scanner input = new Scanner(System.in);
            System.out.println("输入num1");
            double num1 = input.nextDouble();
            System.out.println("输入运算符");
            String oper = input.next();
            System.out.println("输入num2");
            double num2 = input.nextDouble();
    
            //调用工厂类的方法获取具体的计算实例
            Cal cal = Factory.createInstance(oper);
            double result = cal.cal(num1, num2);
            //输出结果
            System.out.println(result);
        }
    }
    
    • 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

    分析:原本的代码,如果后期要添加新的操作(比如乘/除)就需要在原来的代码上进行修改,显然这违背了开放封闭原则。修改的思想是,使用一个计算的接口,每一种操作都实现计算器接口的一项功能,在工厂类中调用该计算器接口,根据不同的操作符生成不同的操作对象。如果要扩展操作功能,就可以扩展Cal接口。此处,其实Factory也不符合开闭原则,可以使用反射对其进行修改:

    //工厂类
    public class Factory {
        public static Cal createInstance(String oper) {
            Cal cal = null;
            try {
                //加载配置文件
                Properties properties = new Properties();
                properties.load(Factory.class.getClassLoader().getResourceAsStream("oper.properties"));
                //获取需要执行类的全类名
                String clz = properties.getProperty(oper);
                System.out.println(clz);
                //动态加载
                Class clazz = Class.forName(clz);
                //创建实例
                 cal = (Cal)clazz.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cal;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以使用配置文件的方式对Factory的功能进行扩充:

    //oper.properties配置文件:
    +=com.zsn.test.Add
    -=com.zsn.test.sub
    
    • 1
    • 2
    • 3

    这样经过上述的修改,如果想要扩充计算方法,只需要在配置文件中添加相应运算符指定新的运算类即可。以上也被称之为简单工厂模式(后文详细讲解)。

    3. 依赖倒置原则

    高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。

    好处: 当两个模块之间存在紧密的耦合关系时,可以在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系。这样,高层模块就不再依赖于底层模块。

    和开放封闭原则的关系:依赖倒置类似于实现开放封闭原则的方法。

    样例:

    //定义奔驰小车类
    class Benz {
    	public void run(){
    		System.out.println("大奔来了,前面的让一让……");
    	}
    }
    
    //定义一个司机类(能开奔驰小车)
    class Driverc {
    	public void drive(Benz benz){
    		System.out.println("老司机开车了,上车的赶快……");
            benz.run();
    	}
    }
    
    public class Client {
    	public static void main( String[] args){
    		Benz benz = new Benz( );
    		Driverc driver = new DriverC();
            //让司机去开奔驰车
    		driver.drive(benz) ;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    优化修改之后:

    //定义小车接口
    interface Car {
    	public void run();
    }
    
    //定义奔驰小车类(实现car接口)
    class Benz implements Car{
    	@override
    	public void run(){
    		System.out.print1n("大奔来了,前面的让一让……");
    	}
    }
    
    //BMW类,实现car接口
    class BMw implements Car i
    	@override
    	public void run() {
    		System.out.println("BMw来了,前面的让一让……");
    	}
    }
    
    //定义一个司机类(能开小车)
    class Driverc {
    	public void drive(car car){
    		System.out.println("老司机开车了,上车的赶快……");
            car.run();
    }
    public class client {
    	public static void main(String[] args) i
    		Car car1 = new Benz();
    		Car car2 = new BMIW();
    		Driverc driver = new DriverC();
       		 //让司机去开奔驰车
    		driver.drive(car1);
        	//让司机去开BMW
        	driver.drive(car2);
    	}
    }
    
    • 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

    分析:原来的程序,司机直接依赖于具体的实现类(大奔),这样如果该司机要开新的车(BMw),就需要对司机的drive函数进行修改,这违背了开放封闭原则。优化的程序中,司机直接调用接口(车)中的方法,而不同类型的车直接实现车接口,就可以做到扩展功能而不修改代码(开闭原则)。

    4. 里氏替换原则

    子类必须可以代替父类。基于该原则可知:在继承时,子类尽量不要重写父类的方法,子类可以扩展新的方法。

    好处: 能保证继承复用是可靠的,父类替换子类不会产生任何问题。

    经典问题:
    “鸵鸟非鸟”:鸟是基类,鸵鸟是鸟的实现类,该类需要覆盖原来的fly()方法,这样鸵鸟就不能代替鸟。
    “长方形不是正方形”:长方形是基类,正方形是子类,正方形计算面积的area()方法需要覆盖基类的方法,这样使用正方形代替长方形是就会出现一些问题。

    样例:

    class A {
        public void fun(int a, int b) {
            System.out.println(a + "+" + b + "=" + (a + b));
        }
    }
    
    class B extends A {
        @Override
        public void fun(int a, int b) {
            System.out.println(a + "-" + b + "=" + (a - b));
        }
    }
    
    public class demo {
        public static void main(String[] args) {
            System.out.println("父类的运行结果");
            A a = new A();
            a.fun(1, 2);
            //父类存在的地方,可以用子类替代
            //子类B替代父类A
            System.out.println("子类替代父类后的运行结果");
            B b = new B();
            b.fun(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

    分析:可以看到,子类的方法如果覆盖的父类的方法,则在使用子类代替父类时结果是不一样的。

    5. 接口隔离原则

    使用多个小的专门的接口,而不要使用一个大的总接口。

    分析:个类对另外一个类的依赖应该建立在最小的接口上,胖接口会导致实现的类型必须完全实现接口的所有方法,显然这是一种浪费。可以通过接口的多继承来实现客户的需求。

    样例:

    /**
    * 定义接口1,里面定义了N个方法
    */
    interface Interface1{
        void fun1();
    	void fun2();
        void fun3();
        void fun4();
        void fun5();
    }
    
    /**
    * Class1类中具有多个无用的方法。
    * 虽然在这里可以不用去写太多的代码,但是在调用的时候会引起混乱
    */
    public class Class1 implements Interface1{
    	@Override
    	public void fun1() {
    		system.out.println("这个方法是有用的.....");
    	}
    	@Override
    	public void fun2() {
    		System.out.println("这个方法是有用的.....");
    	}
    	@Override
    	public void fun3() {
            
        }
        @Override
    	public void fun4() {
            
        }
        @Override
    	public void fun5() {
            
        }
    }
    
    • 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

    使用接口隔离优化的代码:

    /**
    * 首先将胖接口根据具体的需求隔离成多个小的接口
    */
    interface Interface1{
        void fun1();
    	void fun2();
    }
    interface Interface2{
        void fun3();
    	void fun4();
    }
    public interface Interface3{  
    	void fun5();
    }
    
    //如果一个类需要实现方法fun1,fun2,fun5。再定义一个接口,继承接口1和接口3
    //这时,接口4利用接口的多继承特性,就具有了三个方法的定义
    interface Interface4 extends Interface1, Interface3{  
    
    }
    
    //实现类实现接口4
    public class Class1 implements Interface4{
    	@Override
    	public void fun1() {
    		
    	}
    	@Override
    	public void fun2() {
    		
    	}
        @Override
    	public void fun5() {
            
        }
    }
    
    • 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

    分析:使用一个大的接口,如果创建实现类的话需要实现所有的方法。而使用小接口,多接口继承的方式,则避免了不必要的方法实现。

    6. 合成复用原则

    尽量使用对象组合,而不是继承来达到复用的目的。也就是,不要为了继承而继承。

    好处

    • 使用继承的方式来进行代码复用,缺点在于继承来的方法相当于是自身的方法,那么这个方法如果在需求发生变更的时候,就无法改变(必须要修改源代码来达到目的)。这破坏开放封闭原则。
    • 而使用合成(组合)复用,方法是其他类中的方法,在本类中只是去调用其他的类。所以可以使用依赖倒置原则,可以达到在需求发生变更的时候,扩展现有代码达到目的。
    • 组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用

    样例:

    class DBConnection {
    	public String getConnection() {
    	return "MySQL数据库连接";
    	}
    }
    class UserDao extends DBConnection {
    	public void addUser(){
    	//此处复用了父类的方法
    	String connection = super.getConnection();//省略后续添加客户的操作
    	}
    }
    public class Test {
    	public static void main(String[] args){
    		UserDao userDao = new UserDao();
         	userDao.addUser();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    优化后的代码:

    abstract class DBConnection {
    	public abstract String getConnection();
    }
    
    class MySQLConnection extends DBConnection {
    	@Override
    	public String getConnection() {
    		return "MySQL数据库连接";
    }
    class OracleConnection extends DBConnection {
    	@Override
    	public String getConnection() {
    		return "Oracle数据库连接";
    }
    
    class UserDao {
    	private DBConnection connection;
        
        //通过组合(聚合)的方式调用外部对象的方法,外部方法可以使用依赖倒置进行扩展
    	public void setConnection(DBConnection connection) {
    		this.connection = connection;
    	}
    	public void addUser() {
    	//省略后续利用connection添加客户的操作
    	}
    }
    public class Test {
    	public static void main(String[] args) {
    		UserDao userDao = new UserDao();
    		userDao.setConnection(new MySQLConnection());
    	    userDao.addUser();
    	    
    		userDao.setConnection(new OracleConnection());
    	    userDao.addUser();
    	}
    }
    
    • 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

    分析:如果使用继承的方式复用父类中的代码,则需要扩展时,就需要修改父类代码,违背开放封闭原则。优化后,使用组合的方法,相当于调用外部对象的方法,该对象可以通过依赖倒置的原则进行方法扩展。

    7. 迪米特法则(最少知识原则)

    又称为最少知识原则(Least Knowledge Principle,LKP),含义为:不要和“陌生人”说话;只与你的直接朋友通信。

    好处:尽可能少的与其他实体发生相互作用(与其他实体发生关系越多,那么耦合度就越高),降低耦合。

    样例:

    //商品类
    class Goods{
        
    }
    //员工类
    class Staff {
    	public void checkNumber(List<Goods> goods) {
    		System.out.println("目前超市内商品总数为: "+goods.size());
    	}
    }
    //老板类
    class Boss {
    	public void requireCheckNumber(Staff staff) {
    		List<Goods> goodsList = new ArrayList<Goods>();
            for (int i = 0; i<50; i++) {
    			goodsList.add(new Goods());
    		}
    		staff.checkNumber(goodsList);	
    	}
    }
    
    //测试类
    public class Test{
        public static void main(String[] args){
            Boss boss = new Boss();
            Staff staff = new Staff();
    		boss.requireCheckNumber(staff);
    	}
    }
    
    • 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

    代码优化:

    class Boss {
    	public void requireCheckNumber(Staff staff) {
    		staff.checkNumber();
    	}
    }
    
    public class Staff {
    	public void checkNumber() {
    		List<Goods> goodsList = new ArrayList<Goods>();
            for ( int i = 0; i < 50; i++) {
    			goodsList.add(new Goods());
    		}
    		System.out.println("目前超市内商品总数为: "+goodsList.size());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    分析:未优化前,老板类中既出现了商品类,又出现了员工类,不符合迪米特法则。优化后,商品类的信息通过员工类传递给了老板类。

    8. 七大原则总结

    单一职责原则:要求在软件系统中,一个类只负责一个功能领域中的相应职责。
    开闭原则要求:一个软件实体应当对扩展开放,对修改关闭,即在不修改源代码的基础上扩展一个系统的行为。
    里氏代换原则:可以通俗表述为在软件中如果能够使用基类对象,那么一定能够使用其子类对象。
    依赖倒转原则:要求抽象不应该依赖于细节,细节应该依赖于抽象;要针对接口编程,不要针对实现编程。
    接口隔离原则:要求客户端不应该依赖那些它不需要的接口,即将一些大的接口细化成一些小的接口供客户端使用。
    合成复用原则:要求复用时尽量使用对象组合,而不使用继承。
    迪米特法则:要求一个软件实体应当尽可能少的与其他实体发生相互作用。

    三、设计模式

    1. 单例模式

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

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

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

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

    单例模式有以下几种实现方式:

    1.1 懒汉模式

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

    public class Singleton {
        private static Singleton instance;
    
        private Singleton(){};
    
        static Singleton getInstance(){
            if(instance == null){//懒汉模式:要调用时没有才创建
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    class Test{
        public static void main(String[] args) {
            Singleton singleton = Singleton.getInstance();
            System.out.println(singleton.hashCode());
            Singleton singleton2 = Singleton.getInstance();
            System.out.println(singleton2.hashCode());
            /*
            结果为:
            1846274136
    		1846274136
            */
        }
    }
    
    • 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

    1.2 饿汉模式

    描述:这种方式比较常用,但容易产生垃圾对象。
    优点:线程安全,没有加锁,执行效率会提高。
    缺点:类加载时就初始化,浪费内存。

    public class Singleton {
    	//类加载的时候就创建对象
        static private Singleton instance = new Singleton();
    
        private Singleton(){}
    
        static Singleton getInstance(){
            return instance;
        }
    }
    
    class Test{
        public static void main(String[] args) {
            Singleton singleton = Singleton.getInstance();
            System.out.println(singleton.hashCode());
            Singleton singleton2 = Singleton.getInstance();
            System.out.println(singleton2.hashCode());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.3 线程安全懒汉模式

    使用 synchronized关键字对getInstance()方法进行修饰
    优点:线程安全,实现简单
    缺点:效率低下

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

    1.4 双重校验锁(DCL,即 double-checked locking)

    优点:使用synchronized代码块,效率高,且线程安全
    缺点:写起来需要注意volatile关键字修饰单例对象以及双重校验单例是否为空。

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

    1.5 使用静态内部类

    在Singleton类中定义一个静态内部类,静态内部类中包含一个Singleton的实例成员变量。相比于1.1的懒汉模式,静态内部类的初始化发生在静态方法/静态变量调用的时候,于是就不再有资源的浪费。

    优点:线程安全,简单

    public class Singleton {
        private static class InnerClass{
            static final Singleton INSTANCE = new Singleton();
        }
        private Singleton(){}
    
        public static Singleton getInstance(){
            return InnerClass.INSTANCE;//在调用静态成员变量时该变量才会初始化创建一个Singleton实例,且该实例不可变唯一。
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.6 使用枚举

    描述: 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

    这种方式是 Effective Java 作者 Josh Bloch提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

    public enum Singleton {
        INSTANCE;
        public void func(){
            System.out.println("执行了func方法");
        }
    }
    
    class Test{
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.INSTANCE;
            System.out.println(singleton1.hashCode());
    
            Singleton singleton2 = Singleton.INSTANCE;
            System.out.println(singleton2.hashCode());
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    1.7 总结

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

    2.工厂模式

    核心本质:创建对象不使用new,而是交给工厂来完成。将选择实现类、创建对象统一管理和控制,从而将调用者和实现类解耦。

    不使用工厂模式的代码:

    接口:

    public interface Car {
        void name();
    }
    
    • 1
    • 2
    • 3

    Byd类:

    public class Byd implements Car {
        @Override
        public void name() {
            System.out.println("比亚迪");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Tesla类:

    public class Tesla implements Car {
        @Override
        public void name() {
            System.out.println("特斯拉");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试类:

    public class Test {
        public static void main(String[] args) {
            Car car1 = new Byd();
            Car car2 = new Tesla();
    
            car1.name();
            car2.name();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.1 简单工厂模式

    使用简单工厂模式对上述代码进行修改:

    工厂类:

    public class CarFactory {
        public static Car getCar(String string){
            if(string.equals("比亚迪")){
                return new Byd();
            }else if(string.equals("特斯拉")){
                return new Tesla();
            }else return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试类:

    public class Test {
        public static void main(String[] args) {
            Car car1 = CarFactory.getCar("比亚迪");
            Car car2 = CarFactory.getCar("特斯拉");
    
            car1.name();
            car2.name();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    依赖图
    在这里插入图片描述
    优势: 不需要客户自己创建对象,将创建对象的任务交给工厂来完成。如果要扩展其他类型的车,则可以直接添加一个车类实现车接口,然后在工厂方法中创建具体的车对象。

    缺点: 新增其他类型的车,仍然需要修改工厂类。

    2.2 工厂方法模式

    创建一个车工厂接口:

    public interface CarFactory {
        Car getCar();
    }
    
    • 1
    • 2
    • 3

    创建一个比亚迪车工厂:

    public class BydFactory implements CarFactory{
        @Override
        public Car getCar() {
            return new Byd();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建一个特斯拉车工厂:

    public class TeslaFactory implements CarFactory {
        @Override
        public Car getCar() {
            return new Tesla();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试类:

    public class Test {
        public static void main(String[] args) {
            Car car1 = new BydFactory().getCar();
            Car car2 = new TeslaFactory().getCar();
    
            car1.name();
            car2.name();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    依赖关系图:
    在这里插入图片描述

    优势: 相比于简单工厂,如果需要添加新的车类型,只需要创建一个对应类型的车工厂,然后使用该工厂生产某种类型的车即可。满足开放封闭原则。

    缺点: 多了一层嵌套,比较繁琐。另外,如果子类越来越多,如果给每个子类都创建一个工厂,则会使系统中的类成倍增加。

    3. 抽象工厂模式

    定义:抽象工厂提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类,(可以理解为工厂的工厂)。为了解决工厂方法中可能越来越多的类和工厂,抽象工厂将这些产品类进行分组,有些工厂可以同时生产其中的几个产品,这样既减少了工厂的数量,同时也能保证同一工厂生产的产品具有同一工厂的统一风格。

    例如:华为工厂同时生产手机和路由器,小米工厂也生产手机和路由器。避免了工厂方法模式0分别创建华为手机工厂,华为路由器工厂,小米手机工厂,小米路由器工厂这四个工厂,而是只用建立小米和华为这两个工厂即可,每个工厂负责多种产品,比如华为生产的产品分别叫“华为手机”和“华为路由器”,也保证了产品的一致性。

    在这里插入图片描述

    适用场景:
    1.客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
    2.强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
    3.提供一个产品类的库,所有产品以相同接口出现,从而使客户端不依赖于具体的实现。

    优点
    1.可以确保同一工厂生成的产品相互匹配。
    2.可以避免客户端和具体产品代码的耦合。
    3.单一职责原则。 可以将产品生成代码抽取到同一位置, 使得代码易于维护。
    4.开闭原则。 容易扩展厂商,但是不能扩展产品。

    缺点
    由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂。

    实例:
    手机产品接口:

    public interface IphoneProduct {
    
        void openPhone();
        void shutDown();
        void call();
        void sendMES();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    手机实体类:

    //小米手机类
    class XiaomiPhone implements IphoneProduct {
        @Override
        public void openPhone() {
            System.out.println("打开小米手机");
        }
    
        @Override
        public void shutDown() {
            System.out.println("关闭小米手机");
        }
    
        @Override
        public void call() {
            System.out.println("使用小米手机打电话");
        }
    
        @Override
        public void sendMES() {
            System.out.println("使用小米手机发短信");
        }
    }
    //华为手机类
    class HuaWeiPhone implements IphoneProduct {
        @Override
        public void openPhone() {
            System.out.println("打开华为手机");
        }
    
        @Override
        public void shutDown() {
            System.out.println("关闭华为手机");
        }
    
        @Override
        public void call() {
            System.out.println("使用华为手机打电话");
        }
    
        @Override
        public void sendMES() {
            System.out.println("使用华为手机发短信");
        }
    }
    
    • 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

    路由器产品接口:

    public interface IRouterProduct {
    
        void openWifi();
        void shutDown();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    路由器实体类:

    //华为路由器
    class HuaWeiRouter implements IRouterProduct {
        @Override
        public void openWifi() {
            System.out.println("打开华为WiFi");
        }
    
        @Override
        public void shutDown() {
            System.out.println("关闭华为路由器");
        }
    }
    
    //小米路由器实体类
    class XiaomiRouter implements IRouterProduct {
        @Override
        public void openWifi() {
            System.out.println("打开小米WiFi");
        }
    
        @Override
        public void shutDown() {
            System.out.println("关闭小米路由器");
        }
    }
    
    • 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

    产品生产工厂接口:

    public interface ProductFactory {
        IphoneProduct getPhone();
        IRouterProduct getRouter();
    }
    
    • 1
    • 2
    • 3
    • 4

    产品生产具体工厂类:

    //华为工厂
    class HuaweiFactory implements ProductFactory {
        @Override
        public IphoneProduct getPhone() {
            return new HuaWeiPhone();
        }
    
        @Override
        public IRouterProduct getRouter() {
            return new HuaWeiRouter();
        }
    }
    
    //小米工厂
    class XiaomiFactory implements ProductFactory {
        @Override
        public IphoneProduct getPhone() {
            return new XiaomiPhone();
        }
    
        @Override
        public IRouterProduct getRouter() {
            return new XiaomiRouter();
        }
    }
    
    • 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

    客户端测试类:

    public class Client {
        public static void main(String[] args) {
            ProductFactory factory;
            System.out.println("========华为=========");
            factory = new HuaweiFactory();
            IphoneProduct phone = factory.getPhone();
            IRouterProduct router = factory.getRouter();
            phone.openPhone();
            phone.call();
            phone.sendMES();
            phone.shutDown();
    
            router.openWifi();
            router.shutDown();
    
            System.out.println("========小米=========");
            factory = new XiaomiFactory();
            phone = factory.getPhone();
            router = factory.getRouter();
            phone.openPhone();
            phone.call();
            phone.sendMES();
            phone.shutDown();
    
            router.openWifi();
            router.shutDown();
        }
    }
    
    • 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

    在这里插入图片描述

    4. 构造者模式

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

    适用场景:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

    4.1 传统Builder 模式

    构建者模式UML图如下所示:
    在这里插入图片描述
    如上图所示,builder模式有4个角色。

    Product:最终要生成的对象,例如 Computer实例。
    Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()
    ConcreteBuilder: Builder的实现类。
    Director:决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

    样例代码
    第一步:构造Computer类作为产品

    public class Computer {
        private String cpu; //构造时必选参数
        private String ram;  //必选
        private int usbCount; //可选
        private String keyboard; //可选
        private String display; //可选
    
        public Computer(String cpu,String ram){
            this.cpu = cpu;
            this.ram = ram;
        }
    
        public void setCpu(String cpu) {
            this.cpu = cpu;
        }
    
        public void setDisplay(String display) {
            this.display = display;
        }
    
        public void setRam(String ram) {
            this.ram = ram;
        }
    
        public void setKeyboard(String keyboard) {
            this.keyboard = keyboard;
        }
    
        public void setUsbCount(int usbCount) {
            this.usbCount = usbCount;
        }
    
        @Override
        public String toString() {
            return "Computer{" +
                    "cpu='" + cpu + '\'' +
                    ", ram='" + ram + '\'' +
                    ", usbCount=" + usbCount +
                    ", keyboard='" + keyboard + '\'' +
                    ", display='" + display + '\'' +
                    '}';
        }
    }
    
    • 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

    第二步:构造一个建造者(抽象类)

    //建造者,用来使用可选参数构造产品
    public abstract class ComputerBuilder {
        public abstract void setUsbCount(int count);
        public abstract void setKeyboard(String keyboard);
        public abstract void setDisplay(String display);
    
        public abstract Computer getComputer();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第三步:构造真正的建造者(构造者抽象类的实现类)

    //戴尔电脑构造者
    class DellComputerBuilder extends ComputerBuilder {
    
        private Computer computer;
    
        public DellComputerBuilder(String cpu,String ram){
            computer = new Computer(cpu,ram);
        }
        @Override
        public void setUsbCount(int count) {
            computer.setUsbCount(count);
        }
    
        @Override
        public void setKeyboard(String keyboard) {
            computer.setKeyboard(keyboard);
        }
    
        @Override
        public void setDisplay(String display) {
            computer.setDisplay(display);
        }
    
        @Override
        public Computer getComputer() {
            return computer;
        }
    }
    //苹果电脑构造者
    public class AppleComputerBuilder extends ComputerBuilder {
        private Computer computer;
        public AppleComputerBuilder(String cpu,String ram){
            computer = new Computer(cpu,ram);
        }
    
        @Override
        public void setUsbCount(int count) {
            computer.setUsbCount(count); //设置默认值
        }
    
        @Override
        public void setKeyboard(String keyboard) {
            computer.setKeyboard(keyboard);
        }
    
        @Override
        public void setDisplay(String display) {
            computer.setDisplay(display);
        }
    
        @Override
        public Computer getComputer() {
            return computer;
        }
    }
    
    • 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

    第三步:指导者类(Director),用来指导产品参数构造

    public class Director {
        public void makComputer(ComputerBuilder builder){
            if(builder instanceof AppleComputerBuilder){
                builder.setUsbCount(2);
                builder.setDisplay("苹果显示器");
                builder.setKeyboard("苹果键盘");
            }else {
                builder.setUsbCount(2);
                builder.setDisplay("戴尔显示器");
                builder.setKeyboard("戴尔键盘");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试:

    public class Test {
        public static void main(String[] args) {
            Director director = new Director();
            ComputerBuilder computerBuilder = new AppleComputerBuilder("i5处理器","4G内存");
            director.makComputer(computerBuilder);
            Computer computer1 = computerBuilder.getComputer();
            System.out.println(computer1.toString());
    
            ComputerBuilder computerBuilder1 = new DellComputerBuilder("i5处理器","6G内存");
            director.makComputer(computerBuilder1);
            Computer computer2 = computerBuilder1.getComputer();
            System.out.println(computer2.toString());
        }
    }
    
    /*结果;
    Computer{cpu='i5处理器', ram='4G内存', usbCount=2, keyboard='苹果键盘', display='苹果显示器'}
    Computer{cpu='i5处理器', ram='6G内存', usbCount=2, keyboard='戴尔键盘', display='戴尔显示器'}
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    分析:使用构造者模式,对于客户获取产品,它只需要指定cpu和ram两个必须的参数即可,其他的参数直接由构造者来构建产品,同时让指挥者指挥其他可选参数。

    4.2 改进的构造者模式

    上面传统的构造者模式,使用了一个指挥者来指挥可选参数的选择,按照现实意义上,应该由客户来指挥。

    如何实现:
    第一步:在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
    第二步:在Computer中创建一个private的构造函数,参数为Builder类型
    第三步:在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
    第四步:在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
    第五步:在Builder中创建一个build()方法,在其中构建Computer的实例并返回

    // 产品:电脑
    public class Computer {
        private String cpu; //构造时必选参数
        private String ram;  //必选
        private int usbCount; //可选
        private String keyboard; //可选
        private String display; //可选
    
        private Computer(Builder builder){
            this.cpu=builder.cpu;
            this.ram=builder.ram;
            this.usbCount=builder.usbCount;
            this.keyboard=builder.keyboard;
            this.display=builder.display;
        }
    
        public static class Builder{
            private String cpu;//必须
            private String ram;//必须
            private int usbCount;//可选
            private String keyboard;//可选
            private String display;//可选
    
            public Builder(String cpu,String ram){
                this.cpu = cpu;
                this.ram = ram;
            }
    
            public Builder setUsbCount(int usbCount) {
                this.usbCount = usbCount;
                return this;
            }
    
            public Builder setKeyboard(String keyboard) {
                this.keyboard = keyboard;
                return this;
            }
    
            public Builder setDisplay(String display) {
                this.display = display;
                return this;
            }
    
            public Computer build(){
                return new Computer(this);
            }
    
        }
    
        @Override
        public String toString() {
            return "Computer{" +
                    "cpu='" + cpu + '\'' +
                    ", ram='" + ram + '\'' +
                    ", usbCount=" + usbCount +
                    ", keyboard='" + keyboard + '\'' +
                    ", display='" + display + '\'' +
                    '}';
        }
    }
    
    • 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

    测试类:

    public class Test {
        public static void main(String[] args) {
            //在客户端使用链式调用,一步一步的把对象构建出来。
            Computer computer = new Computer.Builder("新特尔","8G三星")
                    .setDisplay("24寸三星")
                    .setUsbCount(3)
                    .setKeyboard("罗技键盘")
                    .build();
            System.out.println(computer.toString());
        }
    }
    /*结果:
    Computer{cpu='新特尔', ram='8G三星', usbCount=3, keyboard='罗技键盘', display='24寸三星'}
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    不使用静态内部类的方式实现
    第一步:创建产品类

    public class Computer {
        private String cpu; //构造时必选参数
        private String ram;  //必选
        private int usbCount; //可选
        private String keyboard; //可选
        private String display; //可选
    
    
        public Computer(String cpu,String ram){
            this.cpu = cpu;
            this.ram = ram;
        }
    
        public void setUsbCount(int usbCount) {
            this.usbCount = usbCount;
        }
    
        public void setKeyboard(String keyboard) {
            this.keyboard = keyboard;
        }
    
        public void setDisplay(String display) {
            this.display = display;
        }
    
        @Override
        public String toString() {
            return "Computer{" +
                    "cpu='" + cpu + '\'' +
                    ", ram='" + ram + '\'' +
                    ", usbCount=" + usbCount +
                    ", keyboard='" + keyboard + '\'' +
                    ", display='" + display + '\'' +
                    '}';
        }
    }
    
    • 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 Builder {
    	//注意set的返回值不为void而是Builder
        abstract Builder setUsbCount(int usbCount);
        abstract Builder setKeyboard(String keyboard);
        abstract Builder setDisplay(String display);
        //返回构建的产品
        abstract Computer build();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第三步:具体的构造者

    public class AppleBuilder extends Builder {
    
        Computer computer;
    
        public AppleBuilder(String cpu,String ram){
            computer = new Computer(cpu,ram);
        }
        @Override
        Builder setUsbCount(int usbCount) {
            computer.setUsbCount(usbCount);
            return this;
        }
    
        @Override
        Builder setKeyboard(String keyboard) {
            computer.setKeyboard(keyboard);
            return this;
        }
    
        @Override
        Builder setDisplay(String display) {
            computer.setDisplay(display);
            return this;
        }
    
        @Override
        Computer build() {
            return computer;
        }
    }
    
    • 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

    第四步:测试

    public class Test {
        public static void main(String[] args) {
        	//在客户端使用链式调用,一步一步的把对象构建出来。
            Builder builder = new AppleBuilder("i7CPU","12G三星内存");
            Computer computer = builder.setDisplay("苹果显示器")
                    .setKeyboard("罗技键盘")
                    .setUsbCount(4)
                    .build();
            System.out.println(computer.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5. 原型模式

    原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    应用场景:创建一个复杂的对象的时候,如果每次都通过new的方式创建代价较高,于是可以选择使用拷贝的方式获取一个一样的克隆对象。这两个对象进行修改互不影响。

    浅拷贝
    第一步:创建原型类,并实现Cloneable接口,实现其中的clone()方法

    public class Product implements Cloneable{
        private String name;
        private Date date;
    
        public Product(String name,Date date){
            this.name = name;
            this.date = date;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setDate(long date) {
            this.date.setTime(date);
        }
    
        @Override
        public String toString() {
            return "Product{" +
                    "name='" + name + '\'' +
                    ", date=" + date +
                    '}';
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    • 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

    第二步:在使用时利用原型对象拷贝克隆对象

    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Product product = new Product("电脑",new Date());
            System.out.println(product.toString());
            System.out.println(product.hashCode());
    
            Product product1 = (Product) product.clone();
            product1.setName("手机");
            System.out.println(product1.toString());
            System.out.println(product1.hashCode());
    
            product1.setDate(1233543534);
            System.out.println(product.toString());
            System.out.println(product1.toString());
        }
    }
    /*结果:
    Product{name='电脑', date=Wed Aug 31 11:10:43 CST 2022}
    1625635731
    Product{name='手机', date=Wed Aug 31 11:10:43 CST 2022}
    1580066828
    Product{name='电脑', date=Thu Jan 15 14:39:03 CST 1970}
    Product{name='手机', date=Thu Jan 15 14:39:03 CST 1970}
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    分析:两次哈希值不同,说明已经是不同的对象,然而当克隆对象修改了date值后,原型对象值也变了,说明这是一个浅拷贝,对于类成员变量,它们仍然指向了同一个对象!

    深拷贝
    将上述代码中的clone()方法改写为:(即手动将原型类中的成员变量对象逐一克隆)

    @Override
        public Object clone() throws CloneNotSupportedException {
            Object obj = super.clone();
            Product product = (Product)obj;
            product.date = (Date) this.date.clone();
            return obj;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试:

    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Product product = new Product("电脑",new Date());
            System.out.println(product.toString());
            System.out.println(product.hashCode());
    
            Product product1 = (Product) product.clone();
            product1.setName("手机");
            System.out.println(product1.toString());
            System.out.println(product1.hashCode());
    
            product1.setDate(1233543534);
            System.out.println(product.toString());
            System.out.println(product1.toString());
        }
    }
    /*结果为:
    Product{name='电脑', date=Wed Aug 31 11:20:36 CST 2022}
    1625635731
    Product{name='手机', date=Wed Aug 31 11:20:36 CST 2022}
    1580066828
    Product{name='电脑', date=Wed Aug 31 11:20:36 CST 2022}
    Product{name='手机', date=Thu Jan 15 14:39:03 CST 1970}
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    分析:副本修改了date,不影响原型中date对象,说明date对象不再是同一个。

    6. 适配器模式

    适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

    这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。

    应用场景: 客户需要的产品与提供商提供的产品没有很好匹配,就可以使用适配器模式,利用提供商提供的产品经过包装适配成客户需要的产品。

    代码示例
    第一步:创建用户需要的产品类。(一般是抽象类/接口)

    //目标:需要一个能产生5v电压的产品
    public interface Target {
        int outPut5V();
    }
    
    • 1
    • 2
    • 3
    • 4

    第二步:创建提供商提供的产品类(实体类)

    //提供的商品:能产生220v电压的产品
    public class Adaptee {
        int outPut220(){
            int a = 220;
            System.out.println(String.format("输出%s电压", a));
            return a;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第三步:创建一个适配器,适配器实现目标产品接口。继承/组合提供的产品类。

    public class Adapter implements Target{
    
        Adaptee adaptee; //组合一个产品类
        public Adapter(Adaptee adaptee){
            this.adaptee = adaptee;
        }
        @Override
        public int outPut5V() {
            int a = adaptee.outPut220(); //将产品类进过一定封装修改,成为用户需要的目标类
            a = a/44;
            System.out.println(String.format("工作输出%s电压", a));
            return a;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第四步:测试

    public static void main(String[] args) {
            Adaptee adaptee = new Adaptee();//创建产品
            Target target = new Adapter(adaptee);//适配产品
            target.outPut5V();
        }
    /*结果:
    输出220电压
    工作输出5电压
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

    7. 桥接模式

    桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

    主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。 例如,不同的厂商制造不同的产品,每一种产品组合起来都是一个类,没增加一个厂商类就增加了很多。为了解决这种问题,引入桥接模式。

    实例代码
    第一步:抽象厂商类

    public abstract class Band {
        abstract void info();
    }
    
    class Lenovo extends Band{
    
        @Override
        void info() {
            System.out.print("联想");
        }
    }
    
    class Apple extends Band{
    
        @Override
        void info() {
            System.out.print("苹果");
        }
    }
    
    class Dell extends Band{
    
        @Override
        void info() {
            System.out.print("戴尔");
        }
    }
    
    • 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

    第二步:抽象产品类

    public abstract class Computer {
        protected Band band;
        abstract void info();
    
        public Computer(Band band){
            this.band = band;
        }
    }
    
    class Desktop extends Computer{
        public Desktop(Band band) {
            super(band);
        }
    
        @Override
        void info() {
            band.info();
            System.out.print("台式机");
        }
    }
    
    class Laptop extends Computer{
        public Laptop(Band band) {
            super(band);
        }
    
        @Override
        void info() {
            band.info();
            System.out.print("笔记本");
        }
    }
    
    class Table extends Computer{
        public Table(Band band) {
            super(band);
        }
    
        @Override
        void info() {
            band.info();
            System.out.print("平板");
        }
    }
    
    • 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

    第三步:测试,并两两组合

    public class Test {
        public static void main(String[] args) {
            Computer computer = new Laptop(new Lenovo());
            computer.info();
    
            System.out.println();
            Computer computer1 = new Table(new Apple());
            computer1.info();
        }
    }
    /*
    联想笔记本
    苹果平板
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    8. 代理模式

    在代理模式(Proxy Pattern)中,一个类代理另一个类的功能。这种类型的设计模式属于结构型模式。

    应用场景:由于职责单一原则,原本的对象可能只负责某一种功能,于是可以使用代理,在原来的基础上新增加一些功能。

    代码示例
    第一步:创建被代理的对象

    public class Host {
        public void rent(){
            System.out.println("租房子");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第二步:创建代理的对象

    interface Rent {//定义一个行为接口
        void rent();
    }
    
    public class Proxy implements Rent{
        private Host host;
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent(){
            look();
            host.rent();
            HeTong();
        }
    
        public void look(){
            System.out.println("看房");
        }
    
        public void HeTong(){
            System.out.println("签合同");
        }
    }
    
    • 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

    第三步:测试

    public class Test {
        public static void main(String[] args) {
            Host host = new Host();
            Proxy proxy = new Proxy(host);
            proxy.rent();
        }
    }
    /*结果:
    看房
    租房子
    签合同
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    9. 装饰器模式

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

    这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

    应用场景:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。在不想增加很多子类的情况下扩展类,可以使用装饰器模式。

    实例
    第一步:先创建一个现有对象的接口,方便现有对象的扩展

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

    第二步:创建现有对象的实体类,该类就是被装饰的对象

    //基础的圆、长方形类
    class Circle implements shape{
        @Override
        public void draw() {
            System.out.println("画圆形");
        }
    }
    
    class Rectangle implements shape{
        @Override
        public void draw() {
            System.out.println("画长方形");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第三步:创建修饰器接口(抽象类),方便对修饰器进行扩展。在抽象类中组合一个被修饰对象。

    public abstract class ShapeDecorator{
        protected shape decoratorShape;
    
        public ShapeDecorator(shape decoratorShape) {
            this.decoratorShape = decoratorShape;
        }
    
        public abstract void draw();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    第四步:创建修饰器实体类

    class RedShapeDecorator extends ShapeDecorator {
        public RedShapeDecorator(shape decoratorShape) {
            super(decoratorShape);
        }
    
        @Override
        public void draw() {
            decoratorShape.draw();
            setRedBorder(decoratorShape);//对原有方法进行修饰
        }
    
        private void setRedBorder(shape decoratedShape){
            System.out.println("边框颜色:红色");
        }
    }
    
    class YellowShapeDecorator extends ShapeDecorator {
    
        public YellowShapeDecorator(shape decoratorShape) {
            super(decoratorShape);
        }
    
        @Override
        public void draw() {
            decoratorShape.draw();
            setRedBorder(decoratorShape); //对原有方法进行修饰
        }
    
        private void setRedBorder(shape shape){
            System.out.println("画框颜色:黄色");
        }
    }
    
    • 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

    第五步:测试

    public class Test {
        public static void main(String[] args) {
            shape shape = new Circle();
            ShapeDecorator decorator = new RedShapeDecorator(shape);
            decorator.draw();
    
            System.out.println("=================");
            ShapeDecorator decorator1 = new RedShapeDecorator(new Rectangle());
            decorator1.draw();
    
            System.out.println("=================");
            ShapeDecorator decorator2 = new YellowShapeDecorator(new Rectangle());
            decorator2.draw();
        }
    }
    /*结果:
    画圆形
    边框颜色:红色
    =================
    画长方形
    边框颜色:红色
    =================
    画长方形
    画框颜色:黄色
    */
    
    • 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
  • 相关阅读:
    爬虫爬取mp3文件例子
    重装系统后edge浏览器老是开机自启动解决方法
    MySQL增删改查语句练习(grade表,student表,subjects表,result表)
    分布式系统中如何实现临界资源的互斥访问?
    介绍一款数据准实时复制(CDC)中间件 `Debezium`
    Banana Pi BPI-5202 龙芯2K1000lA在工业控制行业的应用
    Kamiya丨Kamiya艾美捷人α1-抗糜蛋白酶ELISA说明书
    6-6 循环队列入队出队 分数 10
    【数据结构】栈---C语言版(详解!!!)
    day04 springmvc
  • 原文地址:https://blog.csdn.net/ha_lee/article/details/126530279