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
    评论
现代C++中的设计模式是用于对象重用的可重复性方法。设计模式是一种在不同情况下解决相似问题的经验总结,可以通过将问题解决方案的关键部分抽象出来,从而提供灵活性和可重用性。设计模式不是编程语言特定的功能,而是一种通用的方法论。 在现代C++中,有许多常用的设计模式可以用于对象的可重用性。以下是几个常见的设计模式示例: 1.单例模式:用于确保一个类只能创建一个实例,并提供对该实例的全局访问点。对于有些对象只需要一个实例的情况,单例模式可以确保该实例的唯一性,从而方便访问和管理。 2.工厂模式:用于创建对象的过程中封装创建逻辑,让客户端代码无需关心对象的具体创建细节。通过工厂模式,可以通过一个工厂类来创建对象,从而提供更高的灵活性和可扩展性。 3.观察者模式:用于对象之间的发布-订阅机制,让一个对象(主题)的状态发生变化时,能够通知并自动更新其他依赖于该对象的对象(观察者)。通过观察者模式,可以实现对象之间的松耦合和消息传递,提高对象的可重用性和可维护性。 4.适配器模式:用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以解决接口不兼容的问题,从而使得原本不兼容的类能够一起工作,提高可重用性和互操作性。 5.策略模式:用于定义一系列算法/行为,并将其封装成独立的类,使得它们可以互相替换。策略模式可以在运行时根据需要动态切换算法/行为,从而提供更高的灵活性和可重用性。 这些设计模式都是在现代C++中常见且有用的重用性方法,可以根据具体的应用场景选择合适的设计模式来提高代码的可维护性、可扩展性和可重用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值