登录
  • 欢迎访问悠扬的技术博客,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站😉

设计模式-创建型模式(一)工厂模式

设计模式 悠扬 2293次浏览 已收录

设计模式-创建型模式(一)工厂模式

聊聊工厂

          说说3种不同的实现方式,分别是简单工厂模式工厂方法模式抽象工厂模式

工厂模式定义: :D 定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

1.简单工厂

优点和缺点: :roll: 

优点:

  1. 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
  2. 客户端无需知道所创建具体产品的类名,只需知道参数即可。
  3. 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。

缺点:

  1. 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
  2. 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
  3. 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
  4. 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。

应用场景 8-) 

         对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

印象中的实现

设计模式-创建型模式(一)工厂模式

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

    //抽象产品
    public interface Product {
        void show();
    }

    //具体产品:ProductA
    static class ConcreteProduct1 implements Product {
        public void show() {
            System.out.println("具体产品1显示...");
        }
    }

    //具体产品:ProductB
    static class ConcreteProduct2 implements Product {
        public void show() {
            System.out.println("具体产品2显示...");
        }
    }

    final class Const {
        static final int PRODUCT_A = 0;
        static final int PRODUCT_B = 1;
        static final int PRODUCT_C = 2;
    }

    static class SimpleFactory {
        public static Product makeProduct(int kind) {
            switch (kind) {
                case Const.PRODUCT_A:
                    return new ConcreteProduct1();
                case Const.PRODUCT_B:
                    return new ConcreteProduct2();
            }
            return null;
        }
    }
}

进阶实现  见 com.nxhz.sy.mode.creation.factory.NumberFactory

public interface NumberFactory {

    //实现方式一
    Number parse(String s);

    NumberFactory impl = new NumberFactoryImpl();

    // 获取工厂实例:
    static NumberFactory getFactory() {
        return impl;
    }

    //实现方式二
    /**
     * 简单的类型转换不一定要用工厂,只是一种思想
     * 这样做的好处是可以从如下方法里面进行操作,不一定每次都要新增对象,也可以写缓存代码直接返回     *
     * 类似于Integer.valueOf(100) 源码操作;
     */
    public static Number parse2(String s) {
        return new BigDecimal(s);
    }

}

       使用新特性interface中可以写方法,变量,可以直接在父接口获取实例,可以通过多态来实现创建不同类型的实例,可以在方法中抽象具体业务类,这样做的好处是允许创建产品的代码独立地变换,而不会影响到调用方,类似Integer.valueOf,有兴趣可以看看源码。

       这种简化的使用静态方法创建产品的方式称为静态工厂方法,这种使用方式在java中普遍调用类似Integer.valueOf,看源码好处是有时可以直接在缓存中返回,有时会新建对象,提高了代码执行效率,且不一定每次都会真正创建对象,完全可以返回缓存的对象,从而提升速度并减少内存消耗。

可以学习这种设计思想,提高代码执行效率。

这里有个常用方法了里氏替换原则:List.of()  :?: 

List<String> list = List.of("A", "B", "C");

        这个静态工厂方法接收可变参数,然后返回`List`接口。需要注意的是,调用方获取的产品总是`List`接口,而且并不关心它的实际类型。即使调用方知道`List`产品的实际类型是`java.util.ImmutableCollections$ListN`,也不要去强制转型为子类,因为静态工厂方法`List.of()`保证返回`List`,但也完全可以修改为返回`java.util.ArrayList`。这就是里氏替换原则:返回实现接口的任意子类都可以满足该方法的要求,且不影响调用方。

总是引用接口而非实现类,能允许变换子类而不影响调用方,即尽可能面向抽象编程.

2.工厂方法模式

优缺点应用场景

        对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

       简单工厂模式,提到了简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
  • 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
  • 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。

缺点:

  • 类的个数容易过多,增加复杂度
  • 增加了系统的抽象性和理解难度
  • 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。

应用场景:

  • 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
  • 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  • 客户不关心创建产品的细节,只关心产品的品牌

设计模式-创建型模式(一)工厂模式

结构与实现

工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。本节来分析其基本结构和实现方法。

1. 结构

工厂方法模式的主要角色如下。

  1. 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
  2. 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  3. 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  4. 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

2.实现

public class AbstractFactoryTest {
    public static void main(String[] args) {
        try {
            Product a;
            AbstractFactory af;
            af = (AbstractFactory) ReadXML1.getObject();
            a = af.newProduct();
            a.show();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

//抽象产品:提供了产品的接口
interface Product {
    public void show();
}

//具体产品1:实现抽象产品中的抽象方法
class ConcreteProduct1 implements Product {
    public void show() {
        System.out.println("具体产品1显示...");
    }
}

//具体产品2:实现抽象产品中的抽象方法
class ConcreteProduct2 implements Product {
    public void show() {
        System.out.println("具体产品2显示...");
    }
}

//抽象工厂:提供了厂品的生成方法
interface AbstractFactory {
    public Product newProduct();
}

//具体工厂1:实现了厂品的生成方法
class ConcreteFactory1 implements AbstractFactory {
    public Product newProduct() {
        System.out.println("具体工厂1生成-->具体产品1...");
        return new ConcreteProduct1();
    }
}

//具体工厂2:实现了厂品的生成方法
class ConcreteFactory2 implements AbstractFactory {
    public Product newProduct() {
        System.out.println("具体工厂2生成-->具体产品2...");
        return new ConcreteProduct2();
    }
}

class ReadXML1 {
    //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
    public static Object getObject() {
        try {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File("src/FactoryMethod/config1.xml"));
            //获取包含类名的文本节点
            NodeList nl = doc.getElementsByTagName("className");
            Node classNode = nl.item(0).getFirstChild();
            String cName = "FactoryMethod." + classNode.getNodeValue();
            //System.out.println("新类名:"+cName);
            //通过类名生成实例对象并将其返回
            Class<?> c = Class.forName(cName);
            Object obj = c.newInstance();
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

3.抽象工厂模式

模式的应用场景

抽象工厂模式最早的应用是用于创建属于不同操作系统的视窗构件。如 Java 的 AWT 中的 Button 和 Text 等构件在 Windows 和 UNIX 中的本地实现是不同的。

抽象工厂模式通常适用于以下场景:

  1. 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  2. 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  3. 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

模式的扩展

抽象工厂模式的扩展有一定的“开闭原则”倾斜性

  1. 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
  2. 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。

另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。

设计模式-创建型模式(一)工厂模式

 

  • 抽象工厂模式(Abstract Factory)是一个比较复杂的创建型模式。
  • 抽象工厂模式和工厂方法不太一样,它要解决的问题比较复杂,不但工厂是抽象的,产品是抽象的,而且有多个产品需要创建,因此,这个抽象工厂会对应到多个实际工厂,每个实际工厂负责创建多个实际产品:

设计模式-创建型模式(一)工厂模式

这种模式有点类似于多个供应商负责提供一系列类型的产品。我们举个例子:

假设我们希望为用户提供一个Markdown文本转换为HTML和Word的服务,它的接口定义如下:

public interface AbstractFactory {
    // 创建Html文档:
    HtmlDocument createHtml(String md);
    // 创建Word文档:
    WordDocument createWord(String md);
}

注意到上面的抽象工厂仅仅是一个接口,没有任何代码。同样的,因为HtmlDocumentWordDocument都比较复杂,现在我们并不知道如何实现它们,所以只有接口:

// Html文档接口:
public interface HtmlDocument {
    String toHtml();
    void save(Path path) throws IOException;
}

// Word文档接口:
public interface WordDocument {
    void save(Path path) throws IOException;
}

这样,我们就定义好了抽象工厂(AbstractFactory)以及两个抽象产品(HtmlDocumentWordDocument)。因为实现它们比较困难,我们决定让供应商来完成。

现在市场上有两家供应商:FastDoc Soft的产品便宜,并且转换速度快,而GoodDoc Soft的产品贵,但转换效果好。我们决定同时使用这两家供应商的产品,以便给免费用户和付费用户提供不同的服务。

我们先看看FastDoc Soft的产品是如何实现的。首先,FastDoc Soft必须要有实际的产品,即FastHtmlDocumentFastWordDocument

public class FastHtmlDocument implements HtmlDocument {
    public String toHtml() {
        ...
    }
    public void save(Path path) throws IOException {
        ...
    }
}

public class FastWordDocument implements WordDocument {
    public void save(Path path) throws IOException {
        ...
    }
}

然后,FastDoc Soft必须提供一个实际的工厂来生产这两种产品,即FastFactory

public class FastFactory implements AbstractFactory {
    public HtmlDocument createHtml(String md) {
        return new FastHtmlDocument(md);
    }
    public WordDocument createWord(String md) {
        return new FastWordDocument(md);
    }
}

这样,我们就可以使用FastDoc Soft的服务了。客户端编写代码如下:

// 创建AbstractFactory,实际类型是FastFactory:
AbstractFactory factory = new FastFactory();
// 生成Html文档:
HtmlDocument html = factory.createHtml("#Hello\nHello, world!");
html.save(Paths.get(".", "fast.html"));
// 生成Word文档:
WordDocument word = factory.createWord("#Hello\nHello, world!");
word.save(Paths.get(".", "fast.doc"));

如果我们要同时使用GoodDoc Soft的服务怎么办?因为用了抽象工厂模式,GoodDoc Soft只需要根据我们定义的抽象工厂和抽象产品接口,实现自己的实际工厂和实际产品即可:

// 实际工厂:
public class GoodFactory implements AbstractFactory {
    public HtmlDocument createHtml(String md) {
        return new GoodHtmlDocument(md);
    }
    public WordDocument createWord(String md) {
        return new GoodWordDocument(md);
    }
}

// 实际产品:
public class GoodHtmlDocument implements HtmlDocument {
    ...
}

public class GoodWordDocument implements HtmlDocument {
    ...
}

客户端要使用GoodDoc Soft的服务,只需要把原来的new FastFactory()切换为new GoodFactory()即可。

注意到客户端代码除了通过new创建了FastFactoryGoodFactory外,其余代码只引用了产品接口,并未引用任何实际产品(例如,FastHtmlDocument),如果把创建工厂的代码放到AbstractFactory中,就可以连实际工厂也屏蔽了:

public interface AbstractFactory {
    public static AbstractFactory createFactory(String name) {
        if (name.equalsIgnoreCase("fast")) {
            return new FastFactory();
        } else if (name.equalsIgnoreCase("good")) {
            return new GoodFactory();
        } else {
            throw new IllegalArgumentException("Invalid factory name");
        }
    }
}

参考博客地址:

《廖雪峰的官方网站》

《C语言中文网》

 

 


版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明设计模式-创建型模式(一)工厂模式
喜欢 (1)
支付宝[]
分享 (0)
悠扬
关于作者:
10年以上工作经验:6年以上微服务架构设计搭建经验。 曾任岗位:项目经理、架构师。 擅长领域:大数据、数据库,架构设计,资源优化。 获得业绩: 1.实用新型发明专利1个,修改Apache Sharding源码设计实现分库分表程序增强方案。 2.开源项目一个:https://gitee.com/zsiyang/ruoyi-vue-atomikos (加入开源生态圈)。 3.个人技术博客地址:https://www.nxhz1688.com