Design Patterns in real life: Abstract Factory

The most important thing when we talk about design patterns is to recognize when it can be useful to apply them to design something we have to develop. In this post we see a possible real case application of theAbstract Factory design pattern.
Suppose we have a system that has to process reports that can be of two categories: reports related to transactions in INPUT and reports related to transactions in OUTPUT. For each category there may be different types of reports, such as invoices reports, report related to purchases, etc.. Each report, depending on the category and type, has its own specific processing mode. The reports come in the form of a list of strings that represent the names of the reports themselves, read from a particular folder in the file system. In the name itself reports have indicated their category and their type. Suppose the names of the reports are in the following format: ##name.txt
So, for example:
IN_INV_001.txt -> indicates a report of INPUT category and “invoice” type
OUT_PUR_001.txt -> indicates a report of OUTPUT category and “purchase” type

Let’s see how to use the Abstract Factory pattern to design this kind of system, in order to make itflexible and scalable.
The objects we need are:

  • An interface that defines the method for creating reports, that must be implemented by the concrete factories (IReportFactory)
  • The factory implementations for the two report categories (INReportFactory and OUTReportFactory)
  • The abstraction of our report objects (AREPORT)
  • The abstraction for reports of each category (AINReport and AOUTReport)
  • The concrete implementations of the reports for each category and each type (INInvoiceReport, INPurchaseReport, OUTInvoiceReport, OUTPurchaseReport)
  • The “client” class that uses the reports and that needs to instantiate them(Archive)
  • A factory provider, to decouple the factories instantiation from the client object (FactoryProvider)

As a result of this analysis we can produce the following UML Class Diagram:
Abstract Factory Design Pattern

We can now implement in Java the structures that we have identified:

We start from the report objects abstraction, where we define a String member that contains the name of the report file. We also define a constructor that initializes this member, which will be invoked by the concrete subclasses constructors and a method which defines the processing operations common to all reports, regardless of category or type.

public abstract class AReport {

    protected String name;

    protected AReport(String name) {
        this.name = name;
    }

    public void processReport() {
        System.out.println("Processing report: " + this.name);
    };
}

Now let’s define the abstractions for the two categories of reports, INPUT and OUTPUT, which inherit from the previous one and will add in the override of the processReport() method any common operations at the category level.

public abstract class AINReport extends AReport {

    protected AINReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {

        super.processReport();
        System.out.println("Performing IN Reports common stuff");

    }
}
public abstract class AOUTReport extends AReport {

    protected AOUTReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing OUT Reports common stuff");
    }
}

Now we can create the concrete implementations of our report objects, defining one of them for each category and each type.

public class INInvoiceReport extends AINReport {

    protected INInvoiceReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing IN Reports Invoice specific stuff");
    }
}
public class INPurchaseReport extends AINReport {

    protected INPurchaseReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing IN Reports Purchase specific stuff");
    }
}
public class OUTInvoiceReport extends AOUTReport {

    protected OUTInvoiceReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing OUT Reports Invoice specific stuff");
    }
}
public class OUTPurchaseReport extends AOUTReport {

    protected OUTPurchaseReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing OUT Reports Purchase specific stuff");
    }
}

Now that we have defined the data model of our report, we move to the definition of the creational pattern which will be used to instantiate them depending on the category and type required.
We start from the interface that defines the factory and the method for creating a report that all the concrete factories of the various categories have to implement.

public interface IReportFactory {

    public AReport createReport(String type, String name);

}

We define the two concrete factories for the two categories INPUT and OUTPUT, that will take care of instantiation of the actual reports, based on the required report type.

public class INReportFactory implements IReportFactory {

    @Override
    public AReport createReport(String type, String name) {
        AReport doc = null;
        switch(type) {
            case "INV":
                doc = new INInvoiceReport(name);
            break;
            case "PUR":
                doc = new INPurchaseReport(name);
            break;
            default:
                break;
        }
        return doc;
    }
}
public class OUTReportFactory implements IReportFactory {

    @Override
    public AReport createReport(String type, String name) {
        AReport doc = null;
        switch(type) {
            case "INV":
                doc = new OUTInvoiceReport(name);
            break;
            case "PUR":
                doc = new OUTPurchaseReport(name);
            break;
            default:
                break;
        }
        return doc;
    }
}

At this point we create FactoryProvider, that’s an object that will perform the task to instantiate the correct concrete factory, needed to the creation of the correct report object requested, according to its category and its type. Often, the logic contained in the factory provider is inserted directly in the “client” object that uses the model objects (in our case would be the Archive class), but it is preferable to use this additional level of decoupling, in order to avoid having to change the consumer class in the case of new categories addition (see below an example).

public abstract class FactoryProvider {

    public static IReportFactory getFactory(String factoryType) {
        IReportFactory rf = null;
        switch(factoryType) {
            case "IN":
                    rf = new INReportFactory();
                break;
            case "OUT":
                    rf = new OUTReportFactory();
                break;
            default:
                break;
        }
        return rf;
    }
}

Finally we define the Archive class, which will be our report objects user. It will contain a list of report and a method for the insertion of a new report, which will use the FactoryProvider and the factory interface to instantiate new reports, without relying on concrete implementations. We also add a method to carry out the processing of all reports available in the list.

import java.util.ArrayList;
import java.util.List;

public class Archive {

        private List<AReport> reportList;

        public void addReport(String fam, String type, String name) {
            IReportFactory rf = FactoryProvider.getFactory(fam);
            if (this.reportList == null) {
                this.reportList = new ArrayList<AReport>();
            }
            this.reportList.add(rf.createReport(type, name));
        }

        public void processAllReports() {

            for (AReport r: this.reportList) {
                r.processReport();
                System.out.println("-----");
            }
        }
}

To test if everything works we use a test class, where we create an archive to which we add a list of reports that the system has to process.

public class AbstractFactoryTest {

    public static void main(String[] args) {

        String [] reports = {"IN_INV_001.txt","OUT_PUR_001.txt","IN_INV_002.txt", "IN_PUR_001.txt", "OUT_PUR_002.txt", "OUT_INV_001.txt", "IN_INV_003.txt"};
        String tmp[] = null;
		
        Archive a = new Archive();

        for (String s: reports) {
            tmp = s.split("_");
            a.addReport(tmp[0], tmp[1], s);
        }

        a.processAllReports();
    }
}

Running the test program we get the following result:

Processing report: IN_INV_001.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----
Processing report: OUT_PUR_001.txt
Performing OUT Reports common stuff
Performing OUT Reports Purchase specific stuff
-----
Processing report: IN_INV_002.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----
Processing report: IN_PUR_001.txt
Performing IN Reports common stuff
Performing IN Reports Purchase specific stuff
-----
Processing report: OUT_PUR_002.txt
Performing OUT Reports common stuff
Performing OUT Reports Purchase specific stuff
-----
Processing report: OUT_INV_001.txt
Performing OUT Reports common stuff
Performing OUT Reports Invoice specific stuff
-----
Processing report: IN_INV_003.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----

The advantages of modeling this type of problems with a solution based on a design pattern like the Abstract Factory are its flexibility and scalability, given mainly by the following aspects:

  • Archive depends only on interfaces and does not know anything about how the concrete factories and the related reports are created
  • Adding a new category of reports has no impact on the Archive client class, but it is easily handled in FactoryProvider, simply adding a new case in the switch statement for the instantiation of the new concrete factory
  • Adding a new type of report to an existing category is completely transparent to both the Archive and FactoryProvider classes and it is simply handled in the category concrete factory by adding a new case in the switch statement
  • The FactoryProvider is completely decoupled and can be reused anywhere

Now we try to implement some of the extensions of the system highlighted in the previous list to verify in practice the impacts on the existing code.

Add a new reports category
Suppose we need to add to the system a new report category called “MIXED” which can itself have two types of reports “Invoice” and “Purchase”. To do that we need to create the new abstraction for reports of the MIXED category, extending again the AREPORT abstract class, and the two concrete implementations for puchase and invoice report types for this category.

public abstract class AMIXReport extends AReport {

    protected AMIXReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {

        super.processReport();
        System.out.println("Performing MIX Reports common stuff");
    }
}
public class MIXInvoiceReport extends AMIXReport{

    protected MIXInvoiceReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing MIX Reports Invoice specific stuff");
    }
}
public class MIXPurchaseReport extends AMIXReport{

    protected MIXPurchaseReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {
        super.processReport();
        System.out.println("Performing MIX Reports Purchase specific stuff");
    }
}

We then create the new concrete factory for the new category, that implements the generic factory interface of course.

public class MIXReportFactory implements IReportFactory {

    @Override
    public AReport createReport(String type, String name) {
        AReport doc = null;
        switch(type) {
            case "INV":
                doc = new MIXInvoiceReport(name);
            break;
            case "PUR":
                doc = new MIXPurchaseReport(name);
            break;
            default:
                break;
        }
        return doc;
    }
}

Until now we have only created new classes, without changing anything in the existing code. The only change required is in the FactoryProvider and thst’s ok, because it is exactly the decoupling level we intrdocude and on which we want the changes to be concentrated, because it is a class under our control. So, we modify this class as following:

public abstract class FactoryProvider {

    public static IReportFactory getFactory(String factoryType) {
        IReportFactory rf = null;
        switch(factoryType) {
            case "IN":
                    rf = new INReportFactory();
                break;
            case "OUT":
                    rf = new OUTReportFactory();
                break;
            case "MIX":
                    rf = new MIXReportFactory();
            default:
                break;
        }
        return rf;
    }
}

The Archive class doesn’t go through any changes and it is able to handle new reports of the new category in a completely transparent way!

We modify in the test class the list of reports to be processed, by inserting some of the new category created, and we verify that they are managed properly.

public class AbstractFactoryTest {

    public static void main(String[] args) {

        String [] reports = {"MIX_INV_001.txt","MIX_PUR_002.txt","IN_INV_001.txt","OUT_PUR_001.txt","IN_INV_002.txt", "IN_PUR_001.txt", "OUT_PUR_002.txt", "OUT_INV_001.txt", "IN_INV_003.txt"};

        Archive a = new Archive();

        String tmp[] = null;

        for (String s: reports) {
            tmp = s.split("_");
            a.addReport(tmp[0], tmp[1], s);
        }

        a.processAllReports();
    }
}

The result obtained is the following:

Processing report: MIX_INV_001.txt
Performing MIX Reports common stuff
Performing MIX Reports Invoice specific stuff
-----
Processing report: MIX_PUR_002.txt
Performing MIX Reports common stuff
Performing MIX Reports Purchase specific stuff
-----
Processing report: IN_INV_001.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----
Processing report: OUT_PUR_001.txt
Performing OUT Reports common stuff
Performing OUT Reports Purchase specific stuff
-----
Processing report: IN_INV_002.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----
Processing report: IN_PUR_001.txt
Performing IN Reports common stuff
Performing IN Reports Purchase specific stuff
-----
Processing report: OUT_PUR_002.txt
Performing OUT Reports common stuff
Performing OUT Reports Purchase specific stuff
-----
Processing report: OUT_INV_001.txt
Performing OUT Reports common stuff
Performing OUT Reports Invoice specific stuff
-----
Processing report: IN_INV_003.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----

As we can see the new reports are processed correctly and everything works!

Adding a new report type to an existing category
Now we try to add a new type of report to an existing category. Suppose we want to add the “Order” report type to the existing INPUT category.
We have to create the concrete implementation INOrderReport that extends AINReport.

public class INOrderReport extends AINReport {

    protected INOrderReport(String name) {
        super(name);
    }

    @Override
    public void processReport() {

        super.processReport();
        System.out.println("Performing IN Reports Order specific stuff");
    }
}

The only change to the existing code that we have to do is in the INPUT category reports factory. Also in this case it is not necessary to modify the Archive class and this time neither the FactoryProvider class.

public class INReportFactory implements IReportFactory {

    @Override
    public AReport createReport(String type, String name) {
        AReport doc = null;
        switch(type) {
            case "INV":
                    doc = new INInvoiceReport(name);
                break;
            case "PUR":
                    doc = new INPurchaseReport(name);
                break;
            case "ORD":
                    doc = new INOrderReport(name);
                break;
            default:
                break;
        }
        return doc;
    }
}

We add in the reports list of our test class a report of this new type, such as “IN_ORD_004.txt”, and then we execute the program again.

public class AbstractFactoryTest {

    public static void main(String[] args) {

        String [] reports = {"IN_ORD_004.txt","MIX_INV_001.txt","MIX_PUR_002.txt","IN_INV_001.txt","OUT_PUR_001.txt","IN_INV_002.txt", "IN_PUR_001.txt", "OUT_PUR_002.txt", "OUT_INV_001.txt", "IN_INV_003.txt"};

        Archive a = new Archive();

        String tmp[] = null;

        for (String s: reports) {
            tmp = s.split("_");
            a.addReport(tmp[0], tmp[1], s);
        }

        a.processAllReports();
    }
}

The result obtained is shown below:

Processing report: IN_ORD_004.txt
Performing IN Reports common stuff
Performing IN Reports Order specific stuff
-----
Processing report: MIX_INV_001.txt
Performing MIX Reports common stuff
Performing MIX Reports Invoice specific stuff
-----
Processing report: MIX_PUR_002.txt
Performing MIX Reports common stuff
Performing MIX Reports Purchase specific stuff
-----
Processing report: IN_INV_001.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----
Processing report: OUT_PUR_001.txt
Performing OUT Reports common stuff
Performing OUT Reports Purchase specific stuff
-----
Processing report: IN_INV_002.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----
Processing report: IN_PUR_001.txt
Performing IN Reports common stuff
Performing IN Reports Purchase specific stuff
-----
Processing report: OUT_PUR_002.txt
Performing OUT Reports common stuff
Performing OUT Reports Purchase specific stuff
-----
Processing report: OUT_INV_001.txt
Performing OUT Reports common stuff
Performing OUT Reports Invoice specific stuff
-----
Processing report: IN_INV_003.txt
Performing IN Reports common stuff
Performing IN Reports Invoice specific stuff
-----

The example with all the classes can be downloaded here: 

http://www.davismol.net/2015/03/18/design-patterns-in-real-life-abstract-factory/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值