Design Mode - Abstract Factory Mode

Keywords: Database Oracle

Catalog

Shanzm-1 May 2020 23:20:41

1. Introduction to Modes

Abstract Factory Pattern: Provides an interface for creating a set of related or interdependent objects without specifying their specific classes.

Product family (product family): The products of different levels created by a specific factory are called the same product family, or the same product family.

Note: Products of the same product family inherit from different product abstract classes

The concept of product family exists in the abstract factory mode, but not in the factory method mode, because a specific factory in the factory method mode creates only one specific product.

Product Level: Also known as Product Line, refers to inheriting the same product level as all specific products of the same abstract product class.

To make it easier to understand the family and product grade, hold up a small chestnut:

Abstract Factory Mode main class:

  • AbstractProductA abstracts product class A (or interface), deriving all specific product classes ConcreteProductA1, ConcreteProductA2...

  • AbstractProductB abstracts product class B (or interface), derives all specific product classes ConcreteProductB1, ConcreteProductB2...

  • AbstractFactory abstract factory interface, which is implemented by all specific factory classes

  • ConcreteFactory1 Specific Factory 1, implements IFactory interface, creates specific product objects ConcreteProductA1 and ConcreteProductB1

  • ConcreteFactory2 Specific Factory 2, implements IFactory interface, creates specific product objects ConcreteProductA2 and ConcreteProductB2

Note: Two abstract product classes can be related, such as inheriting or implementing an abstract class or interface together

UML for abstract factory mode:

Note: The original picture is from Design Mode Training - 2nd Edition

Looking closely at UML, you can see that abstract factory mode degrades to factory method mode when there is only one product level in the system.

2. Example 1 - Using factory mode to implement operations on different databases

2.1 Background Description

In practical development, it is possible to change different databases or to use multiple types of databases for a project.
So to facilitate the replacement of different databases, we use factory mode to define different specific factories to create different database operation classes

Sample source, Dahua Design Mode, assumes that a project has both a specific MSSQL database and an Oracle database, but the two databases are of different types, in which the tables and the fields of the tables are the same.

We need to operate on the User tables in both databases.

The following interfaces and classes are implemented in turn according to the design idea of the factory mode:

Abstract Product: IUserService - Declares a way to query and add User table data
Specific products: MSSQLUserService, OracleUserService-Implement IUserService interface for MSSQ and Oracle database respectively
Abstract Factory: IDatabaseFactory - Declares the method for creating IUserService objects
Specific factory: MSSQLFactory, OracleFactory - Implement IDatabaseFactory interface, create MSSQLUserService object and OracleUserService object respectively


2.2 Code implementation

1. Create User Class

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

(2) Create IUserService as the product interface and MSSQLUserService, OracleUserService as the specific product

//Abstract product
public interface IUserService
{
    void Insert(User user);
    User GetUser(int id);
}

//Specific products: Simulate queries and additions to User tables in MSSQL databases
public class MSSQLUserService : IUserService
{
    public void Insert(User user)
    {
        Console.WriteLine($"MSSQL data base User In table-Add a new user,Id:{user.Id },Name{user.Name}");
    }
    public User GetUser(int id)
    {
        Console.WriteLine($"MSSQL data base User In table-Query to user,Id:{id}");
        return null;
    }
}

//Product Specific: Simulate queries and additions to User tables in Oracle databases
public class OracleUserService : IUserService
{
    public void Insert(User user)
    {
        Console.WriteLine($"Oracle data base User In table-Add a new user,Id:{user.Id },Name{user.Name}");
    }
    public User GetUser(int id)
    {
        Console.WriteLine($"Oracle data base User In table-Query to user,Id:{id}");
        return null;
    }
}

(3) Create abstract factory IDatabaseFactory and specific factory MSSQLFactory, OracelFactory

//Abstract factory
public interface IDatabaseFactory
{
    IUserService CreateUserService();
}

//Specific factory: Create MSSQLUserService object
public class MSSQLFactory : IDatabaseFactory
{
    public IUserService CreateUserService()
    {
        return new MSSQLUserService();
    }
}

//Specific factory: Create OracleUserService object
public class OracleFactory : IDatabaseFactory
{
    public IUserService CreateUserService()
    {
        return new OracleUserService();
    }
}

(4) Client Call

static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };

    IDatabaseFactory msSQlFactory = new MSSQLFactory();
    IDatabaseFactory oracleFactory = new OracleFactory();

    //For User tables in MSSQL databases
    IUserService msUserService = msSQlFactory.CreateUserService();
    msUserService.Insert(user);
    msUserService.GetUser(00001);//print: Query to user, Id:00001

    //For User tables in Oracle databases
    IUserService oracleUserService = oracleFactory.CreateUserService();
    oracleUserService.Insert(user);
    oracleUserService.GetUser(00001);//print: Query to user, Id:00001
}

2.3 Program Class Diagram


3. Example 2 - Multi-database and multi-table operations

3.1 Background Description

In Example 1, there are two different databases, each with a User table, and we implemented a User table query and data addition for each database

We used the factory method pattern, which is an abstract product interface (IUserService), implemented by two specific product classes (MSSQLUserService and OracleUserService).

There is an abstract factory interface (IDatabaseFactory) that is implemented by two specific product factory classes (MSSQLFactory and OracleFactory).

Now, if you have a Department table in both databases, you need to manipulate the Department table.

Then you need to modify and add the code as follows:

  • Add an abstract product interface (IDepService) with two specific product classes (MSSQLDepService and OracleDepService) that implement it.

  • Add methods to create MSSQLDepService and OracleDepService objects in the original abstract factory interface and specific factory class.Note that the factory method is extended in the original factory.

3.2 Code implementation

1. Add a Department class based on Example 1

public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }
}

(2) Add a new abstract product interface (IDepService) and implement it with two specific products (MSSQLDepService and OracleDepService)

//Abstract product
public interface IDepartmentService
{
    void Insert(Department dep);
    Department GetDepartment(int id);
}

//Specific products: Simulate queries and additions to the Department table in an MSSQL database
public class MSSQLDepService : IDepartmentService
{
    public Department GetDepartment(int id)
    {
        Console.WriteLine($"MSSQL Database Department In table-Query Department,Id:{id}");
        return null;
    }
    public void Insert(Department dep)
    {
        Console.WriteLine($"MSSQL Database Department In table-Add a new Department,Id:{dep.Id }Name:{dep.Name}");
    }
}

//Specific products: Simulate queries and additions to the Department table in Oracle databases
class OracleDepService : IDepartmentService
{
    public Department GetDepartment(int id)
    {
        Console.WriteLine($"Oracle Database Department In table-Query Department,Id:{id}");
        return null;
    }
    public void Insert(Department dep)
    {
        Console.WriteLine($"Oracle Database Department In table-Add a new Department,Id:{dep.Id }Name:{dep.Name}");
    }
}

(3) On the basis of Example 1, add methods to create MSSQLDepService object and OracleDepService object in original abstract factory interface and specific factory class.

public interface IDatabaseFactory
{
    IUserService CreateUserService();
    IDepartmentService CreateDepService();//Add a new method to the interface
}

public class MSSQLFactory : IDatabaseFactory
{
    public IDepartmentService CreateDepService()
    {
        return new MSSQLDepService();
    }
    public IUserService CreateUserService()
    {
        return new MSSQLUserService();
    }
}

public class OracleFactory : IDatabaseFactory
{
    public IDepartmentService CreateDepService()
    {
        return new OracleDepService();
    }
    public IUserService CreateUserService()
    {
        return new OracleUserService();
    }
}

(4) Call on the client

static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };
    Department dep = new Department() { Id = 1000, Name = "Development" };
   
    IDatabaseFactory DatabaseFactory = new MSSQLFactory();

    //User table operations in MSSQL databases
    IUserService UserService = DatabaseFactory.CreateUserService();
    UserService.Insert(user);
    UserService.GetUser(00001);

    //Del l table operation in MSSQL database
    IDepartmentService DepService = DatabaseFactory.CreateDepService();
    DepService.Insert(dep);
    DepService.GetDepartment(1000);
    Console.ReadKey();
}

Run result:


If you need to change to an Oracle database, all you need to do is modify the new MSSQLFactory() creation specific factory object to the new OracleFactory(), and no other code needs to be modified
static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };
    Department dep = new Department() { Id = 1000, Name = "Development" };
   
    IDatabaseFactory DatabaseFactory = new MSSQLFactory();

    //User table operations in MSSQL databases
    IUserService UserService = DatabaseFactory.CreateUserService();
    UserService.Insert(user);
    UserService.GetUser(00001);

    //Del l table operation in MSSQL database
    IDepartmentService DepService = DatabaseFactory.CreateDepService();
    DepService.Insert(dep);
    DepService.GetDepartment(1000);
    Console.ReadKey();
}

Run result:

3.3 Program Class Diagram

[Instructions]:

  • MSSQLUserService and MSSQLLDepService are created by the same specific factory, MSSQLFactory, that is, they belong to the same product family.

  • OracleUserService and OracleDepService were created by the same specific factory, OracleFactory, that is, they belong to the same product family.

  • When we need to switch databases (that is, switch product families), we only need to modify the creation of specific factory objects: MSSQLFactory objects or OracleFactory objects.This is the greatest advantage of the abstract factory model!


4. Refactoring Example 2 - Improving Abstract factories with simple factories

In the example project above, if you add a new table Student and an operation class for that table, you first need to define an abstract interface, IStudentService, to derive two classes for different database operations: MSSQL StudentService and OracleStudentService, and then add a CreateStudentService() method to the IDatabaseFactory interface, followed by two toolsThe interface is implemented in the factory class of the body.

We can implement the project in Example 2 above using the simple factory model:

Full Demo code download

1 The following interfaces are the same as in Example 2
Abstract product A:IUserService, derived from specific product: MSSQLUserService and OracleUserService
Abstract product B:IDepService, derived from specific product: MSSQLDepService and OracleDepService

(2) Define a simple factory class:
Because there are two abstract products here, what's different from the usual simple factories before is to set up two factory methods:

public class DatabaseFactory
{
    private static readonly string db = "MSSQL";//Change the string to "Oracle" if you need to change the database

    //Factory method for abstract product IUserService
    public static IUserService CreateUserService()
    {
        IUserService userService = null;
        switch (db)
        {
            case "MSSQL":
                userService = new MSSQLUserService();
                break;
            case "Oracle":
                userService = new OracleUserService();
                break;
        }
        return userService;
    }

    //Factory method for abstract product IDepService
    public static IDepartmentService CreateDeprService()
    {
        IDepartmentService depService = null;
        switch (db)
        {
            case "MSSQL":
                depService = new MSSQLDepService();
                break;
            case "Oracle":
                depService = new OracleDepService();
                break;
        }
        return depService;
    }
}

If you need to change the database, simply change the private static readonly string db field assignment to Oracle

(3) Client Calls

static void Main(string[] args)
{
    User user = new User() { Id = 0001, Name = "shanzm" };
    Department dep = new Department() { Id = 1000, Name = "Development" };

    IUserService userService = DatabaseFactory.CreateUserService();
    userService.Insert(user);

    IDepartmentService depService = DatabaseFactory.CreateDeprService();
    depService.Insert(dep);

    Console.ReadKey();
}

Run result:

[Instructions]

  • Using simple factory mode here instead of abstract factory mode simplifies many classes and interfaces, all of which can be modified and added in the factory class

  • Separation of client and instance creation processes is also implemented

(4) Program Class Diagram

Comparing the abstract factory pattern, simply simplify all abstract and specific factories into one factory class, which has two factory methods

5. Refactoring Example 2 - Reflection + Simple Factory

By using reflection we can avoid using switch statements in factory methods.
Get the object name you need to create an instance by reflection, then create an instance object of that class (essentially dependent injection)
It doesn't seem to be much more convenient, but there are actually more case s for switch statements when there are more product families.
So with reflection, you can omit switch ing or not.


Code implementation, 4. Refactoring Example 2 - Modifying the factory class based on using simple factories to improve Abstract factories:

Full Demo code download

public class DatabaseFactory
{
    //The name of the assembly where the specific product is located
    private static readonly string AssemblyName = "04 Abstract Factory Mode-Multiple Database Connections-reflex+Simple factory";
    private static readonly string db = "MSSQL";//Change the string to "Oracle" if you need to change the database

    public static IUserService CreateUserService()
    {
        //Fully qualified name of the specific product
        //Note that because we have special characters in this project, the name of the assembly and the project name are inconsistent. You can right-click the project properties by viewing the assembly name and namespace name
        string className = "_04 Abstract Factory Mode_Multiple Database Connections_reflex_Simple factory" + "." + db + "UserService";
        return (IUserService)Assembly.Load(AssemblyName).CreateInstance(className);
      
    }
    public static IDepartmentService CreateDeprService()
    {
        string className = "_04 Abstract Factory Mode_Multiple Database Connections_reflex_Simple factory" + "." + db + "DepService";
        return (IDepartmentService)Assembly.Load(AssemblyName).CreateInstance(className);
    }
}

6. Refactoring Example 2 - Reflection + Profile + Simple Factory

5. Refactoring Example 2 - Reflection + Simple Factory If data needs to be replaced, or private static readonly string db = "MSSQL" field needs to be modified
Even if you need to modify the code and recompile it, we can put the modified field values in the configuration file

Full Demo code download

Modify 5. Refactoring Example 2 - Reflection + Simple Factory as follows:

First add a reference to "System.Configuration"

(2) Add the following configuration in the configuration file App.Config

<configuration>
  <appSettings>
    <add key="db" value="MSSQL"/><!--Replace data rule<add key="db" value="Oracle"/>-->
  </appSettings>
</configuration>

(3) Modify the db field in the factory class

private static readonly string db = ConfigurationManager.AppSettings["db"];//The value of the db field is read from the configuration file

[Explanation]: Reflection can be used to remove switch ing or if and decouple branch judgment wherever simple factories are used.

7. Summary analysis

For the whole sample project, from Factory Method Mode - > Abstract Factory Mode - > Simple Factory Mode, you can carefully view the program class diagrams of the three implementations, which is worth pondering!

7.1 Advantages

You can see from the UML class diagram that:

  1. Easy to exchange product lines, each specific factory object is only implemented once when it is initialized in the client, so it is very simple to change a specific factory object, so it is easy to change a product sequence.

Simply put, because specific products are created by specific factories, simply modifying specific factory objects is all you need to do when changing product families.

  1. The process of creating a specific product object is separated from the client (as you can see from UML), and the client operates on a specific product instance by manipulating an abstract product interface. The class name of the specific product does not appear on the client.

7.2 Disadvantages

  1. It is very simple to add a new product family by first adding a new specific product to the corresponding product hierarchy and then adding a specific factory.

  2. Adding a new product level can be cumbersome, starting with adding abstract product interfaces, then deriving all specific products, adding methods in abstract factories, and implementing them in all specific factories.

Compare the above to understand:
The expansion of the abstract factory model has a certain inclination of "open and close principle":
When a new product family is added, only a new specific factory needs to be added, and the original code does not need to be modified to meet the open and close principle.
When a new product level is added, all factory classes need to be modified to not meet the open and close principle.


7.3 Suitability

There are multiple product families in the system, but only one of them is used at a time.Switching product families requires only a few modifications to the specific factory object.

For example, in this example, we can implement different product families for different database operations, switching databases only requires simple modification of specific factory objects.


8. Reference and Source Code

Posted by r.smitherton on Sat, 02 May 2020 05:09:04 -0700