设计模式 在线

2238工厂模式

@kusirp21 讲得很好,使用枚举类优化。

增加枚举类

public enum ShapeType{
    CIRCLE,
    RECTANGLE,
    SQUARE
}

修改工厂类的工厂方法,个人建议工厂方法应该是静态方法或者采用单例模式:

public class ShapeFactory{
    public static Shape getShape(ShapeType type){
        switch(type){
            case CIRCLE:
                return new Circle();
            case RECTANGLE:
                return new Rectangle();
            case SQUARE:
                return new Square();
            default:
                throw new UnknownTypeException();
        }
    }
}

上面用到的类和接口与课程中的一样。

最后是使用示例:

public class FactoryPatternDemo {
    public static void main(String[] args) {
        //获取 Circle 的对象,并调用它的 draw 方法
        Shape shape1 = ShapeFactory.getShape(ShapeType.CIRCLE);

        //调用 Circle 的 draw 方法
        shape1.draw();

        //获取 Rectangle 的对象,并调用它的 draw 方法
        Shape shape2 = ShapeFactory.getShape(ShapeType.RECTANGLE);

        //调用 Rectangle 的 draw 方法
        shape2.draw();

        //获取 Square 的对象,并调用它的 draw 方法
        Shape shape3 = ShapeFactory.getShape(ShapeType.SQUARE);

        //调用 Square 的 draw 方法
        shape3.draw();
    }
}

2237工厂模式

使用类名增加耦合性,那就需要使用常量或者枚举类解耦合。

// 接口 增加常量或枚举
public interface Shape {
    static String SHAPE_YUAN = Yuan.class.getName();
    static String SHAPE_FANG = Fang.class.getName();
    static String SHAPE_CHANG = Chang.class.getName();
    void draw();
}

// 工厂方法
public class Factory {
    public static Shape getShape2(String type) {
    try {
        return (Shape) Class.forName(type).getDeclaredConstructor().newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
        return null;
    }
}

// 调用

Shape s21 = Factory.getShape2(Shape.SHAPE_CHANG);
s21.draw();

Shape s22 = Factory.getShape2(Shape.SHAPE_FANG);
s22.draw();

Shape s23 = Factory.getShape2(Shape.SHAPE_YUAN);
s23.draw();

2236工厂模式

使用枚举优化

public enum Factory {
    CIRCLE(new Circle(),"CIRCLE"),
    RECTANGLE(new Rectangle(),"RECTANGLE"),
    SQUARE(new Square(),"SQUARE");
    
    // 成员变量  
    private Shape shape;  
    private String name;  
    
    // 普通方法  
    public static Shape getShape(String name) {  
        for (Factory c : Factory.values()) {  
            if (c.name == name) {  
                return c.shape;  
            }  
        }  
        return null;  
    } 
    // 构造方法  
    private Factory(Shape shape, String name) {  
        this.shape = shape;  
        this.name = name;  
    } 
    public String getName() {
        return name;
    }
    public Shape getShape() {
        return shape;
    }


    public void setShape(Shape shape) {
        this.shape = shape;
    }


    public void setName(String name) {
        this.name = name;
    } 
    
}


/*使用枚举类*/
Factory.getShape("CIRCLE").draw();
Factory.getShape("RECTANGLE").draw();
Factory.getShape("SQUARE").draw();

2235工厂模式

楼上几位真心会开玩笑!

其实使用反射是一种不错的办法,但反射也是从类名反射而不能从类反射!

先看一下工厂模式是用来干什么的——属于创建模式,解决子类创建问题的。换句话来说,调用者并不知道运行时真正的类名,只知道从“Circle"可以创建出一个shape接口的类,至于类的名称是否叫'Circle",调用者并不知情。所以真正的对工厂进行扩展的方式(防止程序员调用出错)可以考虑使用一个枚举类(防止传入参数时,把circle拼写错误)。

如果调用者参肯定类型是Circle的话,那么其工厂没有存在的意义了!

比如 IShape shape = new Circle();这样不是更好?也就是说调用者有了Circle这个知识是可以直接调用的,根据DP(迪米特法则)其实调用者并不知道有一个Circle类的存在,他只需要知道这个IShape接口可以计算圆面积,而不需要知道;圆这个类到底是什么类名——他只知道给定一个”circle"字符串的参数,IShape接口可以自动计算圆的面积就可以了!

其实在.net类库中存在这个模式的的一个典型的。但他引入的另一个概念“可插入编程协议”。

那个就是WebRequest req = WebRequest.Create("http://ccc......");可以自动创建一个HttpWebRequest的对象,当然,如果你给定的是一个ftp地址,他会自动创建一个FtpWebRequest对象。工厂模式中着重介绍的是这种通过某个特定的参数,让你一个接口去干对应不同的事而已!而不是调用者知道了类!

比如如果圆的那个类名叫"CircleShape“呢?不管是反射还是泛型都干扰了你们具体类的生成!其实这个要说明的问题就是这个,调用者(clinet)只知道IShape的存在,在创建时给IShape一个参数"Circle",它可以计算圆的面积之类的工作,但是为什么会执行这些工作,根据迪米特法则,client是不用知道的。

我想问一下那些写笔记的哥们,如果你们知道了泛型,那么为什么不直接使用呢?干吗还需要经过工厂这个类呢?不觉得多余了吗?

如果,我只是说如果,如果所有从IShape继承的类都是Internal类型的呢?而client肯定不会与IShape一个空间!这时,你会了现你根本无法拿到这个类名!

Create时使用注册机制是一种简单的办法,比如使用一个枚举类,把功能总结到一处。而反射也是一种最简单的办法,调用者输入的名称恰是类名称或某种规则时使用,比如调用者输入的是Circle,而类恰是CircleShape,那么可以通过输入+”Shape"字符串形成新的类名,然后从字符串将运行类反射出来!

工厂的创建行为,就这些作用,还被你们用反射或泛型转嫁给了调用者(clinet),那么,这种情况下,要工厂类何用?!

2234工厂模式

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;
   }
}

创建的这个工厂,这个创建函数,没能看出有什么特别的,还特别繁琐,如果IShape类太多,那么if else语句也将增加太多,不好维护,与其这样写,还不如改成如下:

public class ShapeFactory {
    
   //使用 getShape 方法获取形状类型的对象
  public Shape getShape(Class<?> clazz){
        try {
            return (IShape) clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
}

这样写在创建时也不用知道具体的类别,易于管理维护。