在 Java 中应用设计模式 - Factory Method_java中的draw和erase方法-程序员宅基地

技术标签: 模式/分析/管理  exception  Java  java  产品  shapes  class  设计模式  

基本概念

FactoryMethod是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类.当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory Method 模式了.简单说来,Factory Method可以根据不同的条件产生不同的实例,当然这些不同的实例通常是属于相同的类型,具有共同的父类.Factory Method把创建这些实例的具体过程封装起来了,简化了客户端的应用,也改善了程序的扩展性,使得将来可以做最小的改动就可以加入新的待创建的类. 通常我们将Factory Method作为一种标准的创建对象的方法,当发现需要更多的灵活性的时候,就开始考虑向其它创建型模式转化

简单分析

图1是Factory Method 模式的结构图,这里提供了一些术语,让我们可以进行更方便的描述:

  1. Product: 需要创建的产品的抽象类.
  2. ConcreteProduct: Product的子类,一系列具体的产品.
  3. Creator: 抽象创建器接口,声明返回Product类型对象的Factory Method.
  4. ConcreteCreator: 具体的创建器,重写Creator中的Factory Method,返回ConcreteProduct类型的实例.

图1: Factory Method 模式结构
 

由此可以清楚的看出这样的平行对应关系: Product <====> Creator ; ConreteProduct <====> ConreteCreator

抽象产品对应抽象创建器,具体产品对应具体创建器.这样做的好处是什么呢?为什么我们不直接用具体的产品和具体的创建器完成需求呢?实际上我们也可以这样做.但通过Factory Method模式来完成,客户(client)只需引用抽象的Product和Creater,对具体的ConcreteProduct和ConcreteCreator可以毫不关心,这样做我们可以获得额外的好处:

  • 首先客户端可以统一从抽象创建器获取产生的实例,Creator的作用将client和产品创建过程分离开来,客户不用操心返回的是那一个具体的产品,也不用关心这些产品是如何创建的.同时,ConcreteProduct也被隐藏在Product后面,ConreteProduct继承了Product的所有属性,并实现了Product中定义的抽象方法,按照Java中的对象造型(cast)原则,通过ConcreteCreator产生的ConcreteProduct可以自动的上溯造型成Product.这样一来,实质内容不同的ConcreteProduct就可以在形式上统一为Product,通过Creator提供给client来访问.
  • 其次,当我们添加一个新的ConcreteCreator时,由于Creator所提供的接口不变,客户端程序不会有丝毫的改动,不会带来动一发而牵全身的灾难, 这就是良好封装性的体现.但如果直接用ConcreteProduct和ConcreteCreator两个类是无论如何也做不到这点的. 优良的面向对象设计鼓励使用封装(encapsulation)和委托(delegation),而Factory Method模式就是使用了封装和委托的典型例子,这里封装是通过抽象创建器Creator来体现的,而委托则是通过抽象创建器把创建对象的责任完全交给具体创建器ConcreteCreator来体现的.

现在,请再回头看看基本概念中的那段话,开始也许觉得生涩难懂,现在是不是已经明朗化了很多.

下面让我们看看在 Java 中如何实现Factory Method模式,进一步加深对它的认识.

具体实施

先说明一点,用Factory Method模式创建对象并不一定会让我们的代码更短,实事上往往更长,我们也使用了更多的类,真正的目的在于这样可以灵活的,有弹性的创建不确定的对象.而且,代码的可重用性提高了,客户端的应用简化了,客户程序的代码会大大减少,变的更具可读性.

  1. 标准实现: 这里我采用Bruce Eckel 用来描述OO思想的经典例子 Shape.这样大家会比较熟悉一些.我完全按照图1中所定义的结构写了下面的一段演示代码.这段代码的作用是创建不同的Shape实例,每个实例完成两个操作:draw和erase.具体的创建过程委托�oShapeFactory来完成.

    1.a 首先定义一个抽象类Shape,定义两个抽象的方法.

    abstract   class  Shape {
      
    //  勾画shape
       public   abstract   void  draw();
      
    //  擦去 shape
       public   abstract   void  erase();
      
    public  String name;
      
    public  Shape(String aName){
        name 
    =  aName;
      }
    }

    1.b 定义 Shape的两个子类: Circle, Square,实现Shape中定义的抽象方法

    //  圆形子类
    class  Circle  extends  Shape {
      
    public   void  draw() {
        System.out.println(
    " It will draw a circle. " );
      }
      
    public   void  erase() {
        System.out.println(
    " It will erase a circle. " ); 
      }
      
    //  构造函数
       public  Circle(String aName){
        
    super (aName);
      }
    }
    //  方形子类
    class  Square  extends  Shape {
      
    public   void  draw() { 
        System.out.println(
    " It will draw a square. " ); 
      }
      
    public   void  erase() { 
        System.out.println(
    " It will erase a square. " ); 
      }
      
    //  构造函数
       public  Square(String aName){
        
    super (aName);
      }
    }

    1.c 定义抽象的创建器,anOperation调用factoryMethod创建一个对象,并对该对象进行一系列操作.

    abstract   class  ShapeFactory {  
      
    protected   abstract  Shape factoryMethod(String aName);
      
    //  在anOperation中定义Shape的一系列行为
    public   void  anOperation(String aName){
        Shape s 
    =  factoryMethod(aName);
        System.out.println(
    " The current shape is:  "   +  s.name);
        s.draw();
        s.erase();
      }
    }

    1.d 定义与circle和square相对应的两个具体创建器CircleFactory,SquareFactory,实现父类的methodFactory方法

    //  定义返回 circle 实例的 CircleFactory
    class  CircleFactory  extends  ShapeFactory {
      
    //  重载factoryMethod方法,返回Circle对象
       protected  Shape factoryMethod(String aName) {
        
    return   new  Circle(aName  +   "  (created by CircleFactory) " );
      }
    }
      
    //  定义返回 Square 实例的 SquareFactory
    class  SquareFactory  extends  ShapeFactory {
      
    //  重载factoryMethod方法,返回Square对象
    protected  Shape factoryMethod(String aName) {
        
    return   new  Square(aName  +   "  (created by SquareFactory) " );
      }
    }

    1.e 测试类:请注意这个客户端程序多么简洁,既没有罗嗦的条件判断语句,也无需关心ConcreteProduct和ConcreteCreator的细节(因为这里我用anOperation封装了Product里的两个方法,所以连Product的影子也没看见,当然把Product里方法的具体调用放到客户程序中也是不错的).

    class  Main {
      
    public   static   void  main(String[] args){
        ShapeFactory sf1 
    =   new  SquareFactory(); 
        ShapeFactory sf2 
    =   new  CircleFactory();
        sf1.anOperation(
    " Shape one " );
        sf2.anOperation(
    " Shape two " );
      }
    }  

    运行结果如下:

    The current shape is: Shape one (created by SquareFactory)

    It will draw a square.

    It will erase a square.

    The current shape is: Shape two (created by CircleFactory)

    It will draw a circle.

    It will erase a circle.

  2. 参数化的Factory Method: 这种方式依靠指定的参数作为标志来创建对应的实例,这是很常见的一种办法.比如JFC中的BorderFactory就是个很不错的例子. 以下的这个例子是用字符串作为标记来进行判断的,如果参数的类型也不一样,那就可以用到过载函数来解决这个问题,定义一系列参数和方法体不同的同名函数,这里java.util.Calendar.getInstance()又是个极好的例子.参数化的创建方式克服了Factory Method模式一个最显著的缺陷,就是当具体产品比较多时,我们不得不也建立一系列与之对应的具体构造器. 但是在客户端我们必须指定参数来决定要创建哪一个类.

    2.a 我们在第一种方法的基础上进行修改,首先自定义一个的异常,这样当传入不正确的参数时可以得到更明显的报错信息.

    class  NoThisShape  extends  Exception {
      
    public  NoThisShape(String aName) {
        
    super (aName);
      }
    }

    2.b去掉了ShapeFactory的两个子类,改为由ShapeFactory直接负责实例的创建. ShapeFactory自己变成一个具体的创建器,直接用参数化的方法实现factoryMethod返回多种对象.

    abstract   class  ShapeFactory {  
      
    private   static  Shape s;
      
    private  ShapeFactory() {}
        
      
    static  Shape factoryMethod(String aName, String aType)  throws  NoThisShape{
        
    if  (aType.compareTo( " square " ) == 0 )
          
    return   new  Square(aName);
        
    else   if  (aType.compareTo( " circle " ) == 0 )
          
    return   new  Circle(aName);
        
    else   throw   new  NoThisShape(aType);  
      }
      
      
    //  在anOperation中定义Shape的一系列行为
       static   void  anOperation(String aName, String aType)  throws  NoThisShape{
        s 
    =  factoryMethod(aName, aType);
        System.out.println(
    " The current shape is:  "   +  s.name);
        s.draw();
        s.erase();
      }
    }

    2.c 测试类:这里客户端必须指定参数来决定具体创建哪个类.这个例子里的anOperation是静态函数,可以直接引用.

    class  Main {
      
    public   static   void  main(String[] args)  throws  NoThisShape{
        ShapeFactory.anOperation(
    " Shape one " , " circle " );
        ShapeFactory.anOperation(
    " Shape two " , " square " );
        ShapeFactory.anOperation(
    " Shape three " " delta " );
      }
    }

    运行结果如下:

    The current shape is: Shape one
    It will draw a circle.
    It will erase a circle.
    The current shape is: Shape two
    It will draw a square.
    It will erase a square.
    Exception in thread 
    " main "  NoThisShape: delta
            at ShapeFactory.factoryMethod(ShapeFactory.java:
    10 )
            at ShapeFactory.anOperation(ShapeFactory.java:
    15 )
            at Main.main(Main.java:
    5 )
  3. 动态装载机制:

    有的时候我们会把ConcreteProduct的实例传给创建器作为参数,这种情况下,如果在创建器里完成创建过程,就必须判断参数的具体类型(用instanceof),然后才能产生相应的实例,那么比较好的做法是利用Java的动态装载机制来完成这件事.比如:

    我们得到一个Shape的子类s,但不知道具体是那个子类,就可以利用Class类自带的方法newInstance()得到实例

    return (Shape)s.getClass().newInstance();

    这种方法有兴趣得读者可以自己尝试,限于篇幅,不写具体代码出来了.

    后话:

    看完这篇文章后,相信读者对Factory Method模式有一个比较清楚的了解了.我想说的是,我们不仅应该关心一个具体的模式有什么作用,如何去实现这个模式,更应该透过现象看本质,不但知其然,还要知其所以然.要通过对模式的学习加深对面向对象思想的理解,让自己的认识得到升华.Factory Method模式看似简单,实则深刻.抽象,封装,继承,委托,多态,针对接口编程等面向对象中的概念都在这里得到了一一的体现.只有抓住了它的本质,我们才能够不拘于形式的灵活运用,而不是为了使用模式而使用模式.



    参考资料



    关于作者

     

    刘湛,武汉大学信息与计算科学系学士,熟悉 Java 语言和 J2EE 思想,国内多个 Java 论坛版主,现致力于 J2EE 平台上的 eCRM 套件开发。你可以通过 [email protected]与他联系!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhoufoxcn/article/details/1863308

智能推荐

hive使用适用场景_大数据入门:Hive应用场景-程序员宅基地

文章浏览阅读5.8k次。在大数据的发展当中,大数据技术生态的组件,也在不断地拓展开来,而其中的Hive组件,作为Hadoop的数据仓库工具,可以实现对Hadoop集群当中的大规模数据进行相应的数据处理。今天我们的大数据入门分享,就主要来讲讲,Hive应用场景。关于Hive,首先需要明确的一点就是,Hive并非数据库,Hive所提供的数据存储、查询和分析功能,本质上来说,并非传统数据库所提供的存储、查询、分析功能。Hive..._hive应用场景

zblog采集-织梦全自动采集插件-织梦免费采集插件_zblog 网页采集插件-程序员宅基地

文章浏览阅读496次。Zblog是由Zblog开发团队开发的一款小巧而强大的基于Asp和PHP平台的开源程序,但是插件市场上的Zblog采集插件,没有一款能打的,要么就是没有SEO文章内容处理,要么就是功能单一。很少有适合SEO站长的Zblog采集。人们都知道Zblog采集接口都是对Zblog采集不熟悉的人做的,很多人采取模拟登陆的方法进行发布文章,也有很多人直接操作数据库发布文章,然而这些都或多或少的产生各种问题,发布速度慢、文章内容未经严格过滤,导致安全性问题、不能发Tag、不能自动创建分类等。但是使用Zblog采._zblog 网页采集插件

Flink学习四:提交Flink运行job_flink定时运行job-程序员宅基地

文章浏览阅读2.4k次,点赞2次,收藏2次。restUI页面提交1.1 添加上传jar包1.2 提交任务job1.3 查看提交的任务2. 命令行提交./flink-1.9.3/bin/flink run -c com.qu.wc.StreamWordCount -p 2 FlinkTutorial-1.0-SNAPSHOT.jar3. 命令行查看正在运行的job./flink-1.9.3/bin/flink list4. 命令行查看所有job./flink-1.9.3/bin/flink list --all._flink定时运行job

STM32-LED闪烁项目总结_嵌入式stm32闪烁led实验总结-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏6次。这个项目是基于STM32的LED闪烁项目,主要目的是让学习者熟悉STM32的基本操作和编程方法。在这个项目中,我们将使用STM32作为控制器,通过对GPIO口的控制实现LED灯的闪烁。这个STM32 LED闪烁的项目是一个非常简单的入门项目,但它可以帮助学习者熟悉STM32的编程方法和GPIO口的使用。在这个项目中,我们通过对GPIO口的控制实现了LED灯的闪烁。LED闪烁是STM32入门课程的基础操作之一,它旨在教学生如何使用STM32开发板控制LED灯的闪烁。_嵌入式stm32闪烁led实验总结

Debezium安装部署和将服务托管到systemctl-程序员宅基地

文章浏览阅读63次。本文介绍了安装和部署Debezium的详细步骤,并演示了如何将Debezium服务托管到systemctl以进行方便的管理。本文将详细介绍如何安装和部署Debezium,并将其服务托管到systemctl。解压缩后,将得到一个名为"debezium"的目录,其中包含Debezium的二进制文件和其他必要的资源。注意替换"ExecStart"中的"/path/to/debezium"为实际的Debezium目录路径。接下来,需要下载Debezium的压缩包,并将其解压到所需的目录。

Android 控制屏幕唤醒常亮或熄灭_android实现拿起手机亮屏-程序员宅基地

文章浏览阅读4.4k次。需求:在诗词曲文项目中,诗词整篇朗读的时候,文章没有读完会因为屏幕熄灭停止朗读。要求:在文章没有朗读完毕之前屏幕常亮,读完以后屏幕常亮关闭;1.权限配置:设置电源管理的权限。

随便推点

目标检测简介-程序员宅基地

文章浏览阅读2.3k次。目标检测简介、评估标准、经典算法_目标检测

记SQL server安装后无法连接127.0.0.1解决方法_sqlserver 127 0 01 无法连接-程序员宅基地

文章浏览阅读6.3k次,点赞4次,收藏9次。实训时需要安装SQL server2008 R所以我上网上找了一个.exe 的安装包链接:https://pan.baidu.com/s/1_FkhB8XJy3Js_rFADhdtmA提取码:ztki注:解压后1.04G安装时Microsoft需下载.NET,更新安装后会自动安装如下:点击第一个傻瓜式安装,唯一注意的是在修改路径的时候如下不可修改:到安装实例的时候就可以修改啦数据..._sqlserver 127 0 01 无法连接

js 获取对象的所有key值,用来遍历_js 遍历对象的key-程序员宅基地

文章浏览阅读7.4k次。1. Object.keys(item); 获取到了key之后就可以遍历的时候直接使用这个进行遍历所有的key跟valuevar infoItem={ name:'xiaowu', age:'18',}//的出来的keys就是[name,age]var keys=Object.keys(infoItem);2. 通常用于以下实力中 <div *ngFor="let item of keys"> <div>{{item}}.._js 遍历对象的key

粒子群算法(PSO)求解路径规划_粒子群算法路径规划-程序员宅基地

文章浏览阅读2.2w次,点赞51次,收藏310次。粒子群算法求解路径规划路径规划问题描述    给定环境信息,如果该环境内有障碍物,寻求起始点到目标点的最短路径, 并且路径不能与障碍物相交,如图 1.1.1 所示。1.2 粒子群算法求解1.2.1 求解思路    粒子群优化算法(PSO),粒子群中的每一个粒子都代表一个问题的可能解, 通过粒子个体的简单行为,群体内的信息交互实现问题求解的智能性。    在路径规划中,我们将每一条路径规划为一个粒子,每个粒子群群有 n 个粒 子,即有 n 条路径,同时,每个粒子又有 m 个染色体,即中间过渡点的_粒子群算法路径规划

量化评价:稳健的业绩评价指标_rar 海龟-程序员宅基地

文章浏览阅读353次。所谓稳健的评估指标,是指在评估的过程中数据的轻微变化并不会显著的影响一个统计指标。而不稳健的评估指标则相反,在对交易系统进行回测时,参数值的轻微变化会带来不稳健指标的大幅变化。对于不稳健的评估指标,任何对数据有影响的因素都会对测试结果产生过大的影响,这很容易导致数据过拟合。_rar 海龟

IAP在ARM Cortex-M3微控制器实现原理_value line devices connectivity line devices-程序员宅基地

文章浏览阅读607次,点赞2次,收藏7次。–基于STM32F103ZET6的UART通讯实现一、什么是IAP,为什么要IAPIAP即为In Application Programming(在应用中编程),一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给_value line devices connectivity line devices