设计模式扫荡-创建型模式-单例、工厂、抽象工厂、原型

设计模式扫荡-创建型模式-单例、工厂、抽象工厂、原型

单例模式(Singleton Pattern)

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

饿汉单例模式

类加载到内存后,就实例化一个单例,JVM保证线程安全

唯一缺点:不管用到与否,类装载时就完成实例化

public class Mgr01 {
    //将对象设成属性、且静态化、且用final修饰表示不准修改
    private static final Mgr01 INSTANCE = new Mgr01();

    //私有化构造方法,不让外部new此类
    private Mgr01() {};

    //通过方法返回实例
    public static Mgr01 getInstance() {
        return INSTANCE;
    }
	// 获取实例方式 类+方法
    // Mgr01 m2 = Mgr01.getInstance();
}

懒汉单例模式

该模式下类加载的时候不实例化,调用方法的时候再实例化

虽然达到了按需初始化的目的,但却带来线程不安全的问题

public class Mgr03 {
    //作为属性,类加载时先不实例化(new)
    private static Mgr03 INSTANCE; 
	//私有构造方法
    private Mgr03() {
    }
	//调用本方法时再进行实例化
    public static Mgr03 getInstance() {
        //多个线程执行下不安全 会new出多份对象
        if (INSTANCE == null) { 
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }
}

懒汉线程安全单例模式

	//对该方法添加 synchroized关键字 使多个线程串行化执行这个方法
    public static synchroized Mgr03 getInstance() {
        if (INSTANCE == null) { 
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

双重检验锁的单例模式

    public static Mgr06 getInstance() {
        //第一重检验
        if (INSTANCE == null) {          
            //对Mgr06加锁,线程获得该锁对象才能执行下面代码
            synchronized (Mgr06.class) {
                //第二重检验
                if(INSTANCE == null) {  
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

相比方法串行化的方式,该方式减少了同步代码块,提高了效率

在方法串行化模式下,每个线程都得进入方法逻辑判断是否存在对象,在双重检验模式下,线程一旦发现已经存在实例化的对象,直接取用,不用等待

内部类实现单例懒加载

public class Mgr07 {

    private Mgr07() {
    }
    /*
    * 内部类
    * */
    private static class Mgr07Holder {
        //内部类里完成实例化
        private final static Mgr07 INSTANCE = new Mgr07();
    }
    public static Mgr07 getInstance() {
        return Mgr07Holder.INSTANCE;
    }
}

该模式下,加载类Mgr07时不会实例化

同样这种模式线程不安全

工厂模式(Factory Pattern)

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

例如:图形作为接口、其实现类有原形、方形、椭圆形,使用工厂模式获取圆形对象、方形对象、椭圆对象等…

其UML类图如下:

//Shape接口类
public interface Shape {
   void draw();
}

//椭圆类 实现Shape接口并重写draw方法
public class Rectangle implements Shape {
   @Override
   public void draw() {
     // ...
   }
}
//方形类
public class Square implements Shape {
   @Override
   public void draw() {
	//...
   }
}
//圆形类
public class Circle implements Shape {
   @Override
   public void draw() {
	//...
   }
}

创建工厂类,用于创建对象

public class ShapeFactory {
    
   //使用 getShape 方法获取调用者想要调用的对象类型
   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;
   }
}

客户调用方式

      ShapeFactory shapeFactory = new ShapeFactory();
      //获取 Circle 的对象,并调用它的 draw 方法
      Shape shape1 = shapeFactory.getShape("CIRCLE");
      shape1.draw();
      //获取 Rectangle 的对象,并调用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
      shape2.draw();
      //获取 Square 的对象,并调用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");
      shape3.draw();

优点:

  • 对象创建的逻辑细节就完成了隐蔽,调用者只需要告诉工厂需要哪个形状的对象即可获得该对象

  • 调用者只关心该对象的接口,即面向接口编程

  • 如果你想添加一个与”形状“具有相同性质的类,只需要实现Shape接口并重写draw方法即可(这保证了程序的扩展性)

缺点:

  • 每次增加一个产品时,都需要增加一个具体类并修改对象创建工厂(例子中的ShapeFactory类),在一定程度上增加了系统的复杂度

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

抽象工厂(Abstract Factory Pattern)

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。

该超级工厂又称为其他工厂的工厂。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,每个生成的工厂都能按照工厂模式提供对象。

举例:游戏中的角色 其有一系列武器、一系列食物、一系列交通工具。

武器分为 [现代武器:AK47] [魔法武器:魔法棒]

食物分为 [现代食物:面包] [魔法食物:蘑菇]

交通工具分为 [现代工具:汽车] [魔法道具:扫帚]

如何合理创建这些对象呢?

抽象类及实现类定义(工厂模式):

//抽象类+抽象方法

//食物
public abstract class Food {
   abstract void eat();
}
public class Bread extends Food{ //面包
    public void printName() {
        System.out.println("wdm");
    }
}
public class MushRoom extends Food{ //蘑菇
    public void printName() {
        System.out.println("dmg");
    }
}

//交通
public abstract class Vehicle { 
    abstract void go();
}

public class Broom extends Vehicle{ //扫帚
    public void go() {
        System.out.println("Car go wuwuwuwuw....");
    }
}
public class Car extends Vehicle{ //汽车
    public void go() {
        System.out.println("Car go wuwuwuwuw....");
    }
}


//武器
public abstract class Weapon {
    abstract void shoot();
}

public class AK47 extends Weapon{ //AK
    public void shoot() {
        System.out.println("tututututu....");
    }
}
public class MagicStick extends Weapon{ //魔法棒
    public void shoot() {
        System.out.println("diandian....");
    }
}

抽象类与接口:

区别仅仅是语义上的不同:一般归结为 - 名词使用抽象类、形容词使用接口

建立抽象工厂,定义创建不同产品族对象的抽象方法:

public abstract class AbastractFactory {
    abstract Food createFood(); //生成食物
    abstract Vehicle createVehicle(); //生成武器
    abstract Weapon createWeapon(); //生成交通工具
}

创建产品族(现代、魔法)工厂,工厂类要继承抽象工厂类并重写抽象工厂的方法

//现代工厂
public class ModernFactory extends AbastractFactory {
    @Override
    Food createFood() {
        return new Bread();
    }
    @Override
    Vehicle createVehicle() {
        return new Car();
    }
    @Override
    Weapon createWeapon() {
        return new AK47();
    }
}

//魔法工厂
public class MagicFactory extends AbastractFactory {
    @Override
    Food createFood() {
        return new MushRoom();
    }
    @Override
    Vehicle createVehicle() {
        return new Broom();
    }
    @Override
    Weapon createWeapon() {
        return new MagicStick();
    }
}

那么,最终调用代码如下

       //使用抽象工厂的具体族实例化来创建对象 
	   AbastractFactory f = new ModernFactory(); //现代工厂
        Vehicle c = f.createVehicle();
        c.go();
        Weapon w = f.createWeapon();
        w.shoot();
        Food b = f.createFood();
        b.printName();
		
	  //魔法工厂略...

以上代码的UML表示为

虚线箭头表示为依赖,可以分为创建create依赖与引用依赖

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象,即例子中现代工厂只提供现代产品…

缺点:产品族扩展比较困难

建造者模式(Builder Pattern)

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。

一个 Builder 类会一步一步构造最终的对象。

该 Builder 类是独立于其他对象的。

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

建造者模式主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;

由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定

现有一个复杂对象Person

public class Person {
 int id;
 String name;
 int age;
 double weight;
 int score;
 Location loc;
 // setter getter 略
 
 public Person() {}
}

//地址对象Info
class Location {
 String street;
 String roomNo;

 public Location(String street, String roomNo) {
     this.street = street;
     this.roomNo = roomNo;
 }
}

该对象的特征为参数过多,并且一个对象的创建并不需要设置所有的属性,那么如何优雅的构建这个对象呢?

使用建造者模式

package com.mashibing.dp.builder;

public class Person {

    //属性 ... 略

    //构造方法-设置成私有
    private Person() {}

    /**
      * 由PersonBuider充当构造器
      * Prrson作为属性,返回经过加工的person对象
      */
    public static class PersonBuilder {
        
        Person p = new Person();

        //主属性 非空 返回的是Person
        public PersonBuilder basicInfo(int id, String name, int age) {
            p.id = id;
            p.name = name;
            p.age = age;
            // return this 代表 personbuilder对象 
            return this;
        }

        /*
        *  以下为可选属性
        * */
	   //设置重量
        public PersonBuilder weight(double weight) {
            p.weight = weight;
            return this;
        }
		//设置分数属性
        public PersonBuilder score(int score) {
            p.score = score;
            return this;
        }
		//设置地址属性
        public PersonBuilder loc(String street, String roomNo) {
            p.loc = new Location(street, roomNo);
            return this;
        }
		//返回最终设置好的对象
        public Person build() {
            return p;
        }
    }
}

class Location {
    // ... 同上
}

那么主方法调用:

        // 链式调用 与 builder模式  配套使用
        Person p = new Person.PersonBuilder()
                .basicInfo(1, "zhangsan", 18) //主属性参数只顶你
                //.score(20)
                .weight(200)
                //.loc("bj", "23")
                .build();

其中,建造者Buider与实体Person为强关联-组合关系。
建造者模式的经典应用:

  • Java中的StringBuilder

注意:与工厂模式对比,建造者更重视于一个复杂对象的装配顺序

原型模式(Prototype Pattern)

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。

当直接创建对象的代价比较大时,或一个对象的属性已经确定,需要产生很多相同对象的时候,则采用这种模式。

例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

在Java中,自带了原型模式的API

其基本原理为:在内存级别上进行对象的复制

实现原型模式需要实现标记型接口Cloneable

一般会重写clone()方法

//实现 Cloneable 接口 该接口作为标记接口 在运行时作为被检查的因素
class Person implements Cloneable {
    int age = 8;
    int score = 100;
	//对象属性
    Location loc = new Location("bj", 22);
	//重写Object类的clone() method
    @Override
    public Object clone() throws CloneNotSupportedException {
        //使用默认的克隆策略 即Object类的克隆方法
        return super.clone();
    }
}

class Location {
    String street; //街道信息
    int roomNo; //门牌号
    public Location(String street, int roomNo) {
        this.street = street;
        this.roomNo = roomNo;
    }
}

区分深克隆和浅克隆:

以上代码在执行完后,内存模型如下

双方引用类型的属性皆指向同一块内存地址

以上成为浅克隆

如果要实现深克隆,需要对Person原型对象的引用类型属性也进行克隆,并将克隆体的引用类型属性指向新的克隆出来的引用类型

代码如下:

class Person implements Cloneable {
    int age = 8;
    int score = 100;

    Location loc = new Location("bj", 22);
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        //对于引用类型的Location同步进行克隆,并使克隆对象的指针指向它
        p.loc = (Location)loc.clone();
        return p;
    }
}

//Location也需要实现Cloneable接口
class Location implements Cloneable {
    String street;
    int roomNo;
	//含参构造器略...
    
    //覆写clone()方法
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

深克隆的内存模型如下:

需要注意的是

String类型在Java中作为引用类型是比较特殊的

String类型的对象一般存放于JVM中的运行时常量池,同值的串在常量池中只有一份,所以不管是深克隆还是浅克隆,String类型都指向同一块内存地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值