【编程素质】设计模式-工厂模式(Factory Pattern)、 控制反转(IoC)与依赖注入(DI)

1,工厂模式(Factory Pattern)

1)概念

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

2)意图

定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

3)场景

主要解决接口选择的问题。
①日志记录器
记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
②数据库访问
当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
④设计一个连接服务器的框架
需要三个协议,”POP3”、”IMAP”、”HTTP”,可以把这三个作为产品类,共同实现一个接口。

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

2,简单工厂(静态工厂,Simple Factory)

1)概念

利用静态方法定义一个简单的工厂。(注意是一个工厂!)

2)场景

①工厂类负责创建的对象比较少。
②对象决定创建出哪一种产品类的实例,而对如何创建对象不关心。(例如你到肯德基说你要鸡腿,要薯条,要饮料还是,,,这时肯德基是一个工厂,客户端只需要点明自己要什么就行)。

3)优缺点

优:
不需要使用创建对象的方法来实例化对象。

缺:
①扩展性差
如果要我想增加一种形状,除了新增一个形状的具体类,还需要修改工厂类方法。
不能通过继承来改变创建方法的行为。
违反了高内聚责任分配原则。由于工厂类集中了所有实例的创建逻辑,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改
②不同的产品需要不同额外参数的时候不支持。

4)实现

①工厂类是一个具体的类(ShapeFactory ),非接口抽象类。有一个重要的create()方法(getShape),根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。利用if或者 switch创建产品并返回需要的实例。
②create()方法通常是静态的,所以也称之为静态工厂。

5)demo

UML
创建一个 Shape 接口,其子类实现工厂接口,返回的也是一个抽象的产品。
工厂类 ShapeFactory,通过使用ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。

①创建一个接口

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.");
   }
}

③创建一个工厂,生成基于给定信息的实体类的对

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");
      //调用 Circle 的 draw 方法
      shape1.draw();
      //获取 Rectangle 的对象,并调用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
      //调用 Rectangle 的 draw 方法
      shape2.draw();
      //获取 Square 的对象,并调用它的 draw 方法
      Shape shape3 = shapeFactory.getShape("SQUARE");
      //调用 Square 的 draw 方法
      shape3.draw();

6)利用反射实现简单工厂

利用反射Class.forName(clz.getName()).newInstance()实现的简单工厂:

public class StaticNoodlesFactory {
    /**
     * 传入Class实例化面条产品类
     *
     * @param clz
     * @param <T>
     * @return
     */
    public static <T extends INoodles> T createNoodles(Class<T> clz) {
        T result = null;
        try {
            result = (T) Class.forName(clz.getName()).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
//兰州拉面
        INoodles lz = StaticNoodlesFactory.createNoodles(LzNoodles.class);
        lz.desc();
        //泡面
        INoodles pm = StaticNoodlesFactory.createNoodles(PaoNoodles.class);
        pm.desc();

优点:增加一种产品时,不需要修改create()的代码。
缺点:因为Class.forName(clz.getName()).newInstance()调用的是无参构造函数生成对象,它和new Object()是一样的性质,而工厂方法应该用于复杂对象的初始化 ,当需要调用有参的构造函数时便无能为力了,这样像为了工厂而工厂。
不推荐,因为这样和简单的new一个对象一样,工厂方法应该用于复杂对象的初始化, 这样像为了工厂而工厂。

3,工厂方法(Factory method)(推荐)

1)概念

定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个(因为创建者类不需要指定实际创建的产品是哪一个),工厂方法让类把实例化推迟到子类。
是常用的对象创建型设计模式,核心是封装类中不变的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。

2)场景

①有一大堆不同的Pizza类,只有到了运行时,才能指定优化哪一个。这些是变化的把部分,若菜单改变,这部分代码也要改变。此时,提取出善变的这部分为独立类。

3)优缺点

优:
将产品的“实现”从“使用”中解耦。
完全实现“开-闭 原则”,实现了可扩展

4)demo

它的核心结构有四个角色,分别是抽象工厂;具体工厂;抽象产品;具体产品
UML

public class Main {

    public static void main(String[] args){
        /**
         * 工厂模式
         * 根据FoctoryType:"one""two"不同,让类把实例化推迟到子类
         */
        Factory factory = new OneFactory();
        Product product = factory.FactoryMethod("one");
        System.out.println(product.productName);

        product = factory.FactoryMethod("two");
        System.out.println(product.productName);        
    }
}
/**
 * 工厂抽象类
 * 创建一个框架,让子类决定如何实现具体的产品
 * @author luo
 *
 */
public abstract class Factory {

    public Product FactoryMethod(String productType){
        Product product = CreateProduct(productType);
        return product;     
    }
    /**
     * 让子类去实现要生产什么产品
     */
    public abstract Product CreateProduct(String productType);
}
/**
 * 产品抽象类:
 * 所有产品都有实现这个接口,这样使用这些产品的类久可以引用这个类了
 * @author luo
 *
 */
public abstract class Product {
    public String productName;
}
/**
 * 具体产品类
 * @author luo
 *
 */
public class OneProduct extends Product{

    public OneProduct(){
        productName = "oneProduct";
    }
}
/**
 * 具体产品类
 * @author luo
 *
 */
public class TwoProduct extends Product{

    public TwoProduct(){
        productName = "twoProduct";
    }
}
/**
 * 具体工厂类
 * @author luo
 *
 */
public class OneFactory extends Factory{

    @Override
    public Product CreateProduct(String productType) {

        switch(productType){

        case "one":
            return new OneProduct();
        case "two":
            return new TwoProduct();
        default:
            break;
        }
        return null;
    }   
}

5)Android中的应用

①java.util.concurrent.Executors类

java.util.concurrent.Executors类便是一个生成Executor 的工厂 ,其采用的便是 多方法静态工厂模式:
例如ThreadPoolExecutor类构造方法有5个参数,其中三个参数写法固定,前两个参数可配置,如下写:

        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

又如JDK想增加创建ForkJoinPool类的方法了,只想配置parallelism参数,便在类里增加一个如下的方法:

        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

4,抽象工厂

1)概念

提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
这个接口的每个方法都负责创建一个具体产品,同时我们利用抽象工厂的子类来提供这些具体的做法,利用工厂方法来实现具体工厂。

2)场景

一个对象族(或者是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式

例如:
①一个应用需要在三个不同平台上运行,则可以通过抽象工厂模式屏蔽掉操作系统对应用的影响,由不同的产品类去处理与操作系统交互的信息。

3)优缺点

优:
①它分离了具体的类
②它使得易于交换产品系列
③它有利于产品的一致性
缺:
①难以支持新种类的产品
将工厂也抽象后,可能会有类爆炸问题。
如每次拓展新产品种类,不仅卖吃卖喝,我还想卖睡,提供床位服务,这需要修改抽象工厂类,因此所有的具体工厂子类,都被牵连,需要同步被修改。

4)demo

以上介绍的工厂都是单产品系的。抽象工厂是多产品系 (貌似也有产品家族的说法)。
举个例子来说,每个店(工厂)不仅仅卖面条,还提供饮料卖。

提供饮料卖,饮料是产品,先抽象一个产品类,饮料:

public abstract class IDrinks {
    /**
     * 描述每种饮料多少钱
     */
    public abstract void prices();
}

然后实现两个具体产品类: 可乐和水。

public class ColaDrinks extends IDrinks {
    @Override
    public void prices() {
        System.out.println("可乐三块五");
    }
}
public class WaterDrinks extends IDrinks {
    @Override
    public void prices() {
        System.out.println("和我一样的穷鬼都喝水,不要钱~!");
    }
}

抽象饭店,无外乎吃喝(抽象工厂类):

public abstract class AbstractFoodFactory {
    /**
     * 生产面条
     *
     * @return
     */
    public abstract INoodles createNoodles();

    /**
     * 生产饮料
     */
    public abstract IDrinks createDrinks();
}

具体工厂类:兰州大酒店、KFC。

public class LzlmFoodFactory extends AbstractFoodFactory {
    @Override
    public INoodles createNoodles() {
        return new LzNoodles();//卖兰州拉面
    }

    @Override
    public IDrinks createDrinks() {
        return new WaterDrinks();//卖水
    }
}
public class KFCFoodFactory extends AbstractFoodFactory {
    @Override
    public INoodles createNoodles() {
        return new PaoNoodles();//KFC居然卖泡面
    }

    @Override
    public IDrinks createDrinks() {
        return new ColaDrinks();//卖可乐
    }
}

使用:

        AbstractFoodFactory abstractFoodFactory1 = new KFCFoodFactory();
        abstractFoodFactory1.createDrinks().prices();
        abstractFoodFactory1.createNoodles().desc();

        abstractFoodFactory1= new LzlmFoodFactory();
        abstractFoodFactory1.createDrinks().prices();
        abstractFoodFactory1.createNoodles().desc();

5,控制反转(IoC)与依赖注入(DI)(不推荐)

两者意思相近。是工厂模式与反射机制的综合应用。

1)概念

①控制反转(Inversion of Control,IoC)

把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。
IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF中。
IoC(控制反转:Inverse of Control)是Spring容器的内核,AOP、声明式事务等功能在此基础上开花结果。但Spring框架面向企业级开发,Android使用java框架太占内存,不建议使用。

②接口驱动

接口驱动可以提供不同灵活的子类实现,增加代码稳定和健壮性。

2)场景

不重新编译部署也可以增加新的需求和对象的时候。

3)优缺点

①优

控制反转把对象生成放在XML种,当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口),只要修改XML即可,实现了对象的热插拔。

②缺

i>生成一个对象的步骤变复杂了(事实上操作上还是挺简单的),对于不习惯这种方式的人,会觉得有些别扭和不直观。
ii>IoC的对象生成使用了反射变成,损耗了效率,但提高了维护性和灵活性。
iii>重构较为困难,如果更改了类名,还需要在xml文件种手动修改。
缺少IDE重构操作的支持,如果在Eclipse要对类改名,那么你还需要去XML文件里手工去改了,这似乎是所有XML方式的缺陷所在。

4)实现

①依赖查找

容器提供回调接口和上下文条件给组件。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上:容器将调用这些回调方法,从而让应用代码获得相关资源。

从容器的角度描述:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

②依赖注入

依赖注入(Dependency Injection,DI)
DI是一个重要的面向对象编程法则来削减计算机程序的耦合问题M降低了组件之间的耦合性。

从应用程序的角度:应用程序依赖容器创建并注入它所需要的外部资源。

可以使应用程序的配置和依赖性规范与实际的应用程序代码分开。通过文本的配置文件进行应用程序组件间相互关系的配置,而不用重新修改并编译具体的代码。

DI使用接口编程,在对象使用时动态注入。
有3种注入方式:

i>构造函数注入(Contructor Injection)
最简单的依赖注入方式.

public class MovieLister {
    private MovieFinder finder;

    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}

ii>setter注入

public class MovieLister {
    s...
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
}

iii>接口注入
接口注入使用接口来提供setter方法,其实现方式如下。
首先要创建一个注入使用的接口。

public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}
class MovieLister implements InjectFinder {
    ...
    public void injectFinder(MovieFinder finder) {
      this.finder = finder;
    }
    ...
}

猜你喜欢

转载自blog.csdn.net/sunshinetan/article/details/80073964