Java设计模式代码案例 (一):创建型设计模式

Java设计模式代码案例 (一):创建型设计模式
Java 版设计模式代码案例 (二):结构型设计模式
Java 版设计模式代码案例 (三):行为型设计模式

1. 工厂模式(Factory)

工厂模式提供了一种将对象的实例化过程封装在工厂类中的方式。通过使用工厂模式,可以将对象的创建与使用代码分离,提供一种统一的接口来创建不同类型的对象。

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

工厂模式包含以下几个核心角色:

  • 抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
  • 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
  • 抽象工厂(Abstract Factory):声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
  • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。

我们将创建一个 Shape 接口和实现 Shape 接口的实体类。

public interface Shape {  
  
    void draw();  
  
}
public class Rectangle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Rectangle::draw() method.");  
    }  
  
}
public class Square implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Square::draw() method.");  
    }  
  
}
public class Circle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Circle::draw() method.");  
    }  
  
}

下一步是定义工厂类 ShapeFactory

public enum ShapeEnum {  
  
    RECTANGLE(Rectangle.class),  
    SQUARE(Square.class),  
    CIRCLE(Circle.class)
;  

    Class<? extends Shape> clazz;  

    ShapeEnum(Class<? extends Shape> clazz) {  
        this.clazz = clazz;  
    }  

    public Class<? extends Shape> getClazz() {  
        return clazz;  
    }  
  
}
public class ShapeFactory {  
  
    public Shape getShape(ShapeEnum shapeType) throws Exception {  
        return shapeType.getClazz().newInstance();  
    }  
  
}

执行程序,输出结果:

public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        ShapeFactory factory = new ShapeFactory();  
        factory.getShape(ShapeEnum.RECTANGLE).draw();  
        factory.getShape(ShapeEnum.SQUARE).draw();  
        factory.getShape(ShapeEnum.CIRCLE).draw();  
    }  
  
}
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Circle::draw() method.

工厂模式的优点:1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
工厂模式的缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

2. 抽象工厂模式(Abstract Factory)

抽象工厂模式是围绕一个超级工厂创建其他工厂,它提供了一种创建对象的最佳方式。 在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

抽象工厂模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体实现类。通过使用抽象工厂模式,可以将客户端与具体产品的创建过程解耦,使得客户端可以通过工厂接口来创建一族产品。

抽象工厂模式包含以下几个核心角色:

  • 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
  • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
  • 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
  • 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。

我们沿用 工厂模式 的代码例子,下一步是创建抽象工厂类 AbstractFactory

public abstract class AbstractFactory {  
  
    protected abstract Shape getShape(ShapeEnum shape);  
  
}

接着定义工厂类 ShapeFactory,这两个工厂类都是扩展了 AbstractFactory

public class ShapesFactory extends AbstractFactory {  
  
    @Override  
    public Shape getShape(ShapeEnum shapeType) {  
        try {  
            return shapeType.getClazz().newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
}
public enum FactoryEnum {  
  
    SHAPE(ShapesFactory.class);  

    Class<? extends AbstractFactory> clazz;  

    FactoryEnum(Class<? extends AbstractFactory> clazz) {  
        this.clazz = clazz;  
    }  

    public Class<? extends AbstractFactory> getClazz() {  
        return clazz;  
    }  
  
}

执行程序,输出结果:

public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        AbstractFactory factory = FactoryEnum.SHAPE.getClazz().newInstance();  
        factory.getShape(ShapeEnum.RECTANGLE).draw();  
        factory.getShape(ShapeEnum.SQUARE).draw();  
        factory.getShape(ShapeEnum.CIRCLE).draw();  
    }  

}
Inside Rectangle::draw() method.
Inside Square::draw() method.
Inside Circle::draw() method.

抽象工厂的优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
抽象工厂的缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

3. 单例模式(Singleton)

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

注意:

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

(1)懒汉式,线程不安全

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class Singleton1 {  
  
    private static Singleton1 instance;  

    private Singleton1() {  
    }  

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

(2)懒汉式,线程安全

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

public class Singleton2 {  
  
    private static Singleton2 instance;  

    private Singleton2() {  
    }  

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

(3)饿汉式,线程安全

这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

public class Singleton3 {  
  
    private static Singleton3 instance = new Singleton3();  

    private Singleton3() {  
    }  

    public static Singleton3 getInstance() {  
        return instance;  
    }  
  
}

(4)登记式 / 静态内部类

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

public class Singleton5 {  
  
    private static class SingletonHolder {  

        private static final Singleton5 INSTANCE = new Singleton5();  

    }  

    private Singleton5() {  
    }  

    public static final Singleton5 getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
  
}

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

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

getInstance() 的性能对应用程序很关键。

public class Singleton4 {  
  
    private volatile static Singleton4 singleton;  

    private Singleton4() {  
    }  

    public static Singleton4 getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton4.class{  
                if (singleton == null) {  
                    singleton = new Singleton4();  
                }  
            }  
        }  
        return singleton;  
    }  
  
}

4. 建造者模式(Builder)

建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

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

我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。

  • 汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。
  • 冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。

我们将创建一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。

public interface Packing {  
  
    String pack();  
  
}
public class Wrapper implements Packing {  
  
    @Override  
    public String pack() {  
        return this.getClass().getSimpleName();  
    }  
  
}
public class Bottle implements Packing {  
  
    @Override  
    public String pack() {  
        return this.getClass().getSimpleName();  
    }  
  
}

一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类。

public interface Item {  
  
    String name();  

    Packing packing();  

    float price();  
  
}

汉堡 Burger

public abstract class Burger implements Item {  
  
    @Override  
    public Packing packing() {  
        return new Wrapper();  
    }  

    @Override  
    public abstract float price();  

}
public class VegBurger extends Burger {  
  
    @Override  
    public float price() {  
        return 25.0f;  
    }  

    @Override  
    public String name() {  
        return "Veg Burger";  
    }  
  
}
public class ChickenBurger extends Burger {  
  
    @Override  
    public float price() {  
        return 50.5f;  
    }  

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

冷饮 ColdDrink

public abstract class ColdDrink implements Item {  
  
    @Override  
    public Packing packing() {  
        return new Bottle();  
    }  

    @Override  
    public abstract float price();  
  
}
public class Coke extends ColdDrink {  
  
    @Override  
    public float price() {  
        return 30.0f;  
    }  

    @Override  
    public String name() {  
        return "Coke";  
    }  
  
}
public class Pepsi extends ColdDrink {  
  
    @Override  
    public float price() {  
        return 35.0f;  
    }  

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

然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder

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

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

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

    public void showItems() {  
        for (Item item : items) {  
            System.out.print("Item : " + item.name());  
            System.out.print(", Packing : " + item.packing().pack());  
            System.out.println(", Price : " + item.price());  
        }  
    }  
  
}
public class MealBuilder {  
  
    public Meal prepareVegMeal() {  
        Meal meal = new Meal();  
        meal.addItem(new VegBurger());  
        meal.addItem(new Coke());  
        return meal;  
    }  

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

执行程序,输出结果:

public class MainTest {  
  
public static void main(String[] args) throws Exception {  
    MealBuilder mealBuilder = new MealBuilder();  

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

    Meal nonVegMeal = mealBuilder.prepareNonVegMeal();  
    System.out.println("nNon-Veg Meal");  
    nonVegMeal.showItems();  
    System.out.println("Total Cost: " + nonVegMeal.getCost());  
}  
  
}
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0

Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5

建造者模式打的优点:

  1. 分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。
  2. 可以更好地控制构建过程,隐藏具体构建细节。
  3. 代码复用性高,可以在不同的构建过程中重复使用相同的建造者。

建造者模式打的缺点:

  1. 如果产品的属性较少,建造者模式可能会导致代码冗余。
  2. 建造者模式增加了系统的类和对象数量。

5. 原型模式(Prototype)

原型模式是用于创建重复的对象,同时又能保证性能。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。

与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。

public interface Shape {  
  
    void draw();  
  
}
public class Rectangle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Rectangle::draw() method.");  
    }  
  
}
public class Square implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Square::draw() method.");  
    }  
  
}
public class Circle implements Shape {  
  
    @Override  
    public void draw() {  
        System.out.println("Inside Circle::draw() method.");  
    }  
  
}

下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。

public class ShapeCache {  
  
    private static Hashtable<String, Shape> shapeMap  
    = new Hashtable<String, Shape>();  

    public static Shape getShape(String shapeId) {  
        Shape cachedShape = shapeMap.get(shapeId);  
        return (Shape) cachedShape.clone();  
    }  

    // 对每种形状都运行数据库查询,并创建该形状  
    // shapeMap.put(shapeKey, shape);  
    // 例如,我们要添加三种形状  
    public static void loadCache() {  
        Circle circle = new Circle();  
        circle.setId("1");  
        shapeMap.put(circle.getId(), circle);  

        Square square = new Square();  
        square.setId("2");  
        shapeMap.put(square.getId(), square);  

        Rectangle rectangle = new Rectangle();  
        rectangle.setId("3");  
        shapeMap.put(rectangle.getId(), rectangle);  
    }  
  
}

执行程序,输出结果:

public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        ShapeCache.loadCache();  

        Shape clonedShape = (Shape) ShapeCache.getShape("1");  
        System.out.println("Shape : " + clonedShape.getType());  

        Shape clonedShape2 = (Shape) ShapeCache.getShape("2");  
        System.out.println("Shape : " + clonedShape2.getType());  

        Shape clonedShape3 = (Shape) ShapeCache.getShape("3");  
        System.out.println("Shape : " + clonedShape3.getType());  
    }  
  
}
Shape : Circle
Shape : Square
Shape : Rectangle

原型模式的优点:1、性能提高。 2、逃避构造函数的约束。
原型模式的缺点:1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

Java 版设计模式代码案例 (一):创建型设计模式
Java 版设计模式代码案例 (二):结构型设计模式
Java 版设计模式代码案例 (三):行为型设计模式


原文始发于微信公众号(白菜说技术):Java设计模式代码案例 (一):创建型设计模式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/172322.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!