在 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

智能推荐

音视频系列1:流媒体_hds akamai-程序员宅基地

文章浏览阅读1.3k次。1. 使用vlc,自带server安装好vlc软件,然后用如下命令起流Applications/VLC.app/Contents/MacOS/VLC -vvv test.264 –sout ‘#rtp{sdp=rtsp://:5544/test}’;vlc会自动创建server,不错哦。2. 使用ffmpeg,nginx做server参考这里mac貌似自带ffmpeg,没有的话就安装一个,然后ffmpeg -re -i test.mp4 -vcodec copy -codec copy -_hds akamai

pytorch 之 nn.BatchNorm2d(oup)( 100 )_batchnorm2d(100)-程序员宅基地

文章浏览阅读2w次,点赞2次,收藏9次。先看看解释。。。。。然后。。。我的疑惑在于:网络片段:nn.Conv2d(inp, oup, 3, stride, 1, bias=False),nn.BatchNorm2d(oup),nn.ReLU(inplace=True),我打印model的parameters来查看参数:打印的为:0.conv.0.weight : torch.Size([32, 3, 3, 3])0.conv.1...._batchnorm2d(100)

MATLAB 画图,对数坐标轴_matlab 对数轴-程序员宅基地

文章浏览阅读1w次,点赞6次,收藏23次。对数坐标轴_matlab 对数轴

迟到的第一篇博客-程序员宅基地

文章浏览阅读194次。其实很早之前就一直在使用CSDN论坛查阅资料,学习新知识。但是直到2019年下旬才注册了账号,当时是因为每天看到不同的大牛博主更新的博文点燃了我的创作激情(毕竟我初中时做过语文课代表),结果注册了账号之后因为工作比较忙碌,也就没有维护。(当初的豪言壮语早被抛掷脑后)先说说自己的情况吧,本人,男,老家来自陕西渭南(谁在说我们渭南没程序猿我跟谁急),之前的专业是法律事务,至于为什么会踏上程序猿这条不归路我以后还会再讲,现在在北京北漂工作一年多了,目前刚刚从上家公司跳槽,现在在一家私企做linux系统开发。因为

PIC18F47K42 初学篇-1_pic18f46k40 学习-程序员宅基地

文章浏览阅读795次。5月1日开始学习PIC18系列单片机,之前一直用MSP430的16位单片机,技能总是太单一,稳定性不是很好。决定回到8位PIC来看看,从PIC18F47K42开始吧,有一个小红板方便开始学习,配合官网资料、手把手教你学PIC单片机、PIC微控制器项目设计。书本主要是加速作用,并没有推荐意义。MPLAB X IDE应该来说还是很不错,就是占用内存太多,运行时硬盘咳咳作响。MCC配置功能还..._pic18f46k40 学习

支付宝踩过的坑sign check fail: check Sign and Data Fail��JSON also��_支付宝 check sign fail!-程序员宅基地

文章浏览阅读751次。支付宝接口弄好以后发送请求报错 数据解析错误:基本上是来源于支付宝 公私钥配置错误 生成好的公钥 重新生成:调试:问题解决_支付宝 check sign fail!

随便推点

PoseCNN: A Convolutional Neural Network for 6D Object Pose Estimation in Cluttered Scenes—2017(笔记)-程序员宅基地

文章浏览阅读1.6k次,点赞2次,收藏7次。PoseCNN:用卷积神经网络估计杂乱场景中目标6D姿态—2017(笔记)文章提出了新的PoseCNN姿态估计网络,通过CNN提取图像特征,然后分三路进行目标分割标签标注、平移估计和姿态估计得到目标6D姿态,其中通过应用新型损失函数,能够较好地估计对称目标。 ----------------- Occlusion、symmetric object、only RGB摘要..._posecnn: a convolutional neural network for 6d object pose estimation in clu

Bootstrap(三): form表单_bootstrap3 form-程序员宅基地

文章浏览阅读9.3k次。 Bootstrap(二): 栅格系统点击打开链接如果和表单熟悉结课起来用会有非常强大的功能,网上对于bootstrap的学习资源很多,表单作为一个学习重点,很多大佬在自己的博客中都分享了自己对表单的理解,在这里我推荐一篇自己认为关于bootstrap表单学习写得很详细的文章:http://www.cnblogs.com/sankexin/p/5509955.html点击打开链接 ..._bootstrap3 form

Ucenter后台登陆 验证码CCCC的解决方法 无法登录解决办法_/uccp-server/login?appcode=&service=http%3a%2-程序员宅基地

文章浏览阅读198次。Ucenter后台登陆 验证码CCCC的解决方法 无法登录解决办法_/uccp-server/login?appcode=&service=http%3a%2

zabbix监控硬件及服务(详解)一_zabbix监控服务器硬件-程序员宅基地

文章浏览阅读2.8w次,点赞22次,收藏111次。大家好今天给大家带来zabbix3.4.8监控主机,那么最近由于我个人的关系。没有及时的更新文章所以,很抱歉那么今天我分享的内容是zabbix3.4.8监控服务器。本章的具体监控服务器如下:服务器的CPU使用率 服务器的硬盘挂载使用率 服务器的网卡流量流入流出使用率 服务器的用户登录终端数量 Web服务器状态码检测那么本章主要就是监控这几个方面。搭建环境流程安装c..._zabbix监控服务器硬件

php乘方开根号,JavaScript_教你JS中的运算符乘方、开方及变量格式转换,1)如何计算乘方 题一:3的4 - phpStudy...-程序员宅基地

文章浏览阅读211次。教你JS中的运算符乘方、开方及变量格式转换1)如何计算乘方题一:3的4次方(不会打,请原谅 ==!!!)3的4次方=3*3*3*3var a = Math.pow(3,4);console.log(a);说明:Math.pow()是用来计算乘方的语法注意:Math的M是大写;题二:3的4*5次方var a =Math.pow(3,4*5);console.log(a);2)如何计算根号题目:根号8..._php有根号运算符吗

java枚举 取值_秒懂系列,超详细Java枚举教程!!!-程序员宅基地

文章浏览阅读4.6k次。深入理解Java枚举一、什么是枚举1.1 什么是枚举?至于枚举,我们先拿生活中的枚举来入手,然后再引申Java中的枚举,其实它们的意义很相似。谈到生活中的枚举,假如我们在玩掷骰子的游戏,在我们手中有两个骰子,要求掷出两个骰子的点数和必须大于6的概率,那么在此情此景,我们就需要使用枚举法一一列举出骰子点数的所有可能,然后根据列举出来的可能,求出概率。可能有的小伙伴发现,这就是数学啊?这就是数学中的概..._枚举取值

推荐文章

热门文章

相关标签