【设计模式】1、创建型——工厂模式

工厂模式(Factory Pattern)是我们在编码中经常会用到的一种设计模式,它属于一种创建型的设计模式,也就是说,对于一个面向对象型的语言来说,这是一种创建对象的比较好的方式。在客户端使用时,无需知道创建对象的逻辑,只需要根据自己的需求,获得一个共同的接口,这个接口变量指向的便是我们需要的实现类对象。

一、使用场景

需要根据不同的条件创建不同的对象,具体创建出什么样的对象,由我们客户端决定,这个工厂只需要提供一个简单的创建对象的接口就行。
比如:我们有一个后台服务程序,需要处理多种协议的报文,json,xml,定长报文,心跳报文,我们会为每一种协议的解析对应一个解析器的类,那么怎么根据不同的报文来获取不同的解析器的处理类,这里我们就可以考虑使用工厂模式,这样不仅简化了分发的流程,而且便于扩展,当以后新增通讯协议,不用修改主要的处理流程,只需根据要求增加对应的解析类即可。

图1 简单粗暴的方法

图2 工厂模式

从图一图二的流程图可以知道,工厂模式使得客户端不用关注解析类是怎么创建的,只需要从工厂方法里取就好了,当增加协议时,图一会随着协议的增加,分支也会越来越多,图二基本保持不变。

二、简单工厂模式

那么现在我们使用简单工厂模式来实现上述的场景。

首先我们创建一个结果对象Request,用来接收解析成功后的结果。

/**
 * Created by mitch on 2018/1/17.
 */
public class Request {
    //被谁解析的
    private String parser;
    /*报文中的内容*/
    private String name;

    public String getParser() {
        return parser;
    }

    public void setParser(String parser) {
        this.parser = parser;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Request{");
        sb.append("parser='").append(parser).append('\'');
        sb.append(", name='").append(name).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

再创建一个解析类的统一接口RequestParser,用来接收工厂返回的实现类实例。

/**
 * Created by mitch on 2018/1/17.
 */
public interface RequestParser {
    Request parse(String message);
}

创建各个解析报文的实现类,并实现parse方法。

XML解析类XmlRequestParser

/**
 * Created by mitch on 2018/1/17.
 */
public class XmlRequestParser implements RequestParser{
    @Override
    public Request parse(String message) {
        System.out.println("XmlRequestParser正在解析...");
        String name = "张三";//假设解析出来的内容为张三
        Request request = new Request();
        request.setName(name);
        request.setParser("XmlRequestParser");
        return request;
    }
}

json解析类JsonRequestParser

/**
 * Created by mitch on 2018/1/17.
 */
public class JsonRequestParser implements RequestParser {
    @Override
    public Request parse(String message) {
        System.out.println("JsonRequestParser正在解析...");
        String name = "张三";//假设解析出来的内容为张三
        Request request = new Request();
        request.setName(name);
        request.setParser("JsonRequestParser");
        return request;
    }
}

最后创建解析器的工厂,用于生产不同的解析器类。

/**
 * Created by mitch on 2018/1/17.
 */
public class ParserFactory {
    public static RequestParser getPaser(String message){
        String protocol = getProtocol(message);
        if("xml".equals(protocol)){
            return new XmlRequestParser();
        }else if("json".equals(protocol)){
            return new JsonRequestParser();
        }else{
            throw new RuntimeException(String.format("不支持的协议[%s]",protocol));
        }
    }

    /**
     * 简单模拟获取不同的报文的协议类型
     *
     * @param message
     * @return
     */
    private static String getProtocol(String message){
        if(message.startsWith("{")&&message.endsWith("}")){
            return "json";
        }else if(message.startsWith("<")&&message.endsWith(">")){
            return "xml";
        }else {
            return "unkown";
        }
    }
}

客户端代码:

/**
 * Created by mitch on 2018/1/17.
 */
public class Client {
    public static void main(String[] args) {
        String message = "{name:张三}";
        System.out.println(String.format("接收到消息:[%s]",message));
        RequestParser parser = ParserFactory.getPaser(message);
        Request request = parser.parse(message);
        System.out.println(String.format("解析得到的结果[%s]",request));
    }
}

运行得到结果:


图3 简单工厂方法运行结果

从工厂类的代码中,我们可以看到,随着协议越来越多,工厂类的if/else分支也会越来越多,那么我们可以通过配置文件加上反射的方式,如:配置文件中配置jsonParserClass=com.xx.JsonRequestParser等,通过协议类型获取到不同的class名称,再通过反射的方式获取到实例。这里不再详细说明,有兴趣的可以自己动手尝试。

三、工厂方法模式

看了简单工厂方法以后,我们发现,如果要新增协议时,需要修改工厂类的代码。这样对程序的扩展性还是不利,所以我们可以将原来的工厂作为一个抽象类,让其子类去实现创建对象,也就是很多个工厂类实现一个核心的抽象工厂类,核心抽象工厂类提供一个创建具体对象的抽象方法,让子类在这个方法中创建具体对象。
那么我们使用工厂方法模式以后,我们实例的工厂类和客户端的代码变成如下:

抽象工厂:ParserFactory修改为:

/**
 * Created by mitch on 2018-01-17.
 */
public abstract class ParserFactory {
    public abstract RequestParser createParser();
}

新增对应的具体功能的工厂:
XmlParserFactory

/**
 * Created by mitch on 2018-01-17.
 */
public class XmlParserFactory extends ParserFactory {
    @Override
    public RequestParser createParser() {
        //这里可以不再是简单的创建对象,还可以对对象进行初始化,
        // 或者进行一些与创建对象相关的复杂逻辑
        return new XmlRequestParser();
    }
}

JsonParserFactory

/**
 * Created by Mitch on 2018-01-17.
 */
public class JsonParserFactory extends ParserFactory {
    @Override
    public RequestParser createParser() {
        //这里可以不再是简单的创建对象,还可以对对象进行初始化,
        // 或者进行一些与创建对象相关的复杂逻辑
        return new JsonRequestParser();
    }
}

客户端代码修改为:

/**
 * Created by mitch on 2018-01-17.
 */
public class Client {
    public static void main(String[] args) {
        //这里的工厂可以通过反射或者Spring动态配置
        ParserFactory parserFactory = new XmlParserFactory();
        String message = "<name>张三</name>";
        System.out.println(String.format("接收到消息:[%s]",message));
        Request request = parserFactory.createParser().parse(message);
        System.out.println(request);
    }
}

运行结果:


图4 工厂方法模式运行结果

工厂方法模式和简单工厂模式的最大的区别就是工厂方法模式多了一层具体的工厂类,客户端在调用时需要根据不同的需求获取不同的工厂类,再创建具体的对象。由此可见:
简单工厂模式的优缺点:简单,客户端做的事情非常少,除去了对具体对象的依赖。但将所有创建具体对象的代码挤在一块,不符合开闭原则,扩展性不好。
工厂方法模式优缺点:扩展性好,没有随着具体对象种类的增多而增加代码分支。但它将具体选择分发的逻辑转移到了客户端;而且增加了一层类关系,增加了一定的开发工作量。

四、抽象工厂模式

通过工厂方法模式我们可以知道,每一个工厂仅能创建一类对象,而且工厂方法模式中会出现大量的工厂类,无疑会增加复杂度和开发量。因此可以将工厂方法中的每个工厂类的职责变大,让它可以生产更多的对象。
假设我们系统现在不仅要支持xml、json、其他协议的解析,还要支持这些协议报文的组装和截取。那么如果按照工厂方法模式的话需要3种协议*3种功能=9个具体工厂类分别负责生产:解析、组装、截取这三种协议的解析类。这种开发体验肯定是很不好的,所以我们可以让每一个具体工厂类都支持生产处理这三种协议的对象,每一个具体工厂类对应一种操作,比如解析报文的工厂,组装报文的工厂,截取报文的工厂。


图5 抽象工厂模型

上图中,对同一种协议的各种操作属于一个产品等级结构,对各个协议的同一个操作为一个产品族,在抽象工厂模式中,每一个具体的工厂都可以生产一个产品族的对象,也就是说,解析工厂可以负责生产所有协议的解析对象,组装工厂可以负责生产所有协议的组装对象,以此类推。

现在有需要生产的对象如下:

每种协议对应的抽象处理类AbstractJsonHandler、AbstractXmlHandler

/**
 * Created by mitch on 2018/1/18.
 */
public abstract class AbstractJsonHandler<T,K>{
    public abstract T hand(K param);
}
/**
 * Created by mitch on 2018/1/18.
 */
public abstract class AbstractXmlHandler<T,K>{
    public abstract T hand(K param);
}

对每个协议进行具体操作的类JsonParser、JsonPackager、XmlPackager、XmlParser等

/**
 * Created by mitch on 2018/1/18.
 */
public class JsonPackager extends AbstractJsonHandler<String,Request> {
    @Override
    public String hand(Request param) {
        System.out.println("JsonPackager正在包装");
        //对报文进行包装
        return "{message:包装好的Json报文}";
    }
}

/**
 * Created by mitch on 2018/1/18.
 */
public class JsonParser extends AbstractJsonHandler<Request,String> {
    @Override
    public Request hand(String param) {
        System.out.println("JsonMessageParser正在解析...");
        String name = "张三";//假设解析出来的内容为张三
        Request request = new Request();
        request.setName(name);
        request.setParser("JsonParser");
        return request;
    }
}
/**
 * Created by mitch on 2018/1/18.
 */
public class XmlPackager extends AbstractXmlHandler<String,Request> {
    @Override
    public String hand(Request param) {
        System.out.println("XmlPackager正在包装报文");
        //包装xml报文
        return "<message>包装好的xml报文</message>";
    }
}
/**
 * Created by mitch on 2018/1/18.
 */
public class XmlParser extends AbstractXmlHandler<Request,String> {
    @Override
    public Request hand(String param) {
        System.out.println("XmlMessageParser正在解析...");
        String name = "张三";//假设解析出来的内容为张三
        Request request = new Request();
        request.setName(name);
        request.setParser("XmlParser");
        return request;
    }
}

抽象工厂MessageHandlerFactory,用来提供生产一个产品族的多个抽象方法

/**
 * Created by mitch on 2018/1/18.
 */
public abstract class MessageHandlerFactory{
    public abstract AbstractJsonHandler getJsonHandler();
    public abstract AbstractXmlHandler getXmlHandler();
    //其他协议
}

具体工厂,对应对一个产品族的处理:
PackageFactory 打包工厂

/**
 * Created by mitch on 2018/1/18.
 */
public class PackageFactory extends MessageHandlerFactory{
    @Override
    public AbstractJsonHandler getJsonHandler() {
        return new JsonPackager();
    }

    @Override
    public AbstractXmlHandler getXmlHandler() {
        return new XmlPackager();
    }
}

ParserFactory工厂,解析工厂

/**
 * Created by mitch on 2018/1/18.
 */
public class ParserFactory extends MessageHandlerFactory {
    @Override
    public AbstractJsonHandler getJsonHandler() {
        return new JsonParser();
    }

    @Override
    public AbstractXmlHandler getXmlHandler() {
        return new XmlParser();
    }
}



客户端代码

/**
 * Created by mitch on 2018/1/18.
 */
public class Client {
    public static void main(String[] args) {
        //该工厂可以进行配置或者动态注入
        MessageHandlerFactory factory = new PackageFactory();

        AbstractJsonHandler<String,Request> jsonHandler = factory.getJsonHandler();
        System.out.println(jsonHandler.hand(new Request()));

        AbstractXmlHandler<String,Request> xmlHandler = factory.getXmlHandler();
        System.out.println(xmlHandler.hand(new Request()));
    }
}

运行结果


图6 抽象工厂运行结果

现在我们来梳理下抽象工厂中的类关系

MessageHandlerFactory

抽象工厂类,提供生产一个产品族的抽象方法

PackageFactory、ParserFactory

具体工厂类,生产具体的对象,结合了协议和处理方法

它们的关系为:


图7 工厂类关系图
AbstractJsonHandler、AbstractXmlHandler

需要创建的抽象类,接收具体工厂创建的对象

JsonPackager、JsonParser、XmlPackager、XmlParser

需要创建的具体对象
它们的关系为:


图8 产品关系图

通过代码我们不难发现,如果新增一种协议,每个具体工厂类都要新增一个对应协议的创建方法,不符合开闭原则,但是新增一种处理方法很简单,只用新增对应的操作类和具体工厂就可以。
所以我们在用这个模式时,要考虑我们的产品族中是否以后还会新增一个类型,相当于是否还有协议要增加。
如果协议要经常增加,但处理方法就解析组装截取这几个比较稳定,可以把产品族换成处理方式,将图5的横轴和纵轴调换一下,这样便可以了。设计模式具体应该怎么使用,一定要跟实际的场景相结合。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352