design-patternsfactory-patternabstract-factoryfactory-method

Is there a design-pattern that addresses creation of the same product in multiple different ways (requiring pipeline-like pre-creation steps)


I am currently working on a machine learning project and would like my Python program to be able to process/convert measurement data from various measurement data formats into a PyTorch compatible dataset class. This essentially means that I need to extract samples and labels from these measurements so that I can instantiate my dataset class.

Right now: I am mainly using a single libraryA which provides all functions I need to load and preprocess the data. To extract the samples and labels as needed I have to follow several processing steps which is why I decided to encapsulate that logic in a simple factory class.

My concern is: What if I need to handle a data format that is not supported by libraryA but by another libraryB. That libraryB, however, has a very different approach on how to extract the samples and labels and requires different processing steps.

My initial thought: Use an abstract factory or a factory method to create the dataset object and let the subclass decide how to do it. But, despite that I am not following the intent of abstract factories / factory method as stated by the GoF (I always want the same single product), the signatures of the abstract methods won't match because the libraries require very different inputs.

My question: Is there a suitable Design-Pattern that standardizes the creation of the same product with very different pre-creation steps?
Or should I stick to concrete simple factories that are tightly coupled to the library (e.g. LibADatasetFactory and LibBDatasetFactory)?


Solution

  • Is there a suitable Design-Pattern that standardizes the creation of the same product with very different pre-creation steps?

    If the same product should be created but by using various approaches or strategies, then Strategy pattern can be used. So one creation strategy will use libraryA, while another strategy will use libraryB to create an object. As wiki says about Strategy pattern:

    In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use

    Let me show an example via C#.

    We will create Person by various libraries:

    public class Person 
    {
        public string Name { get; set; }
    }
    

    And this is an abstraction of creation of Person that will use various libraries:

    public interface IPersonCreationByLibrary 
    {
        Person Create();
    }
    

    And this is a concrete implementation of abstraction to create Person by various libaries:

    public class PersonCreationByLibraryA : IPersonCreationByLibrary
    {
        
        public Person Create()
        {
            // use here libraryA to create a person
            return new Person() { Name = "Person A"};
        }
    }
    

    and:

    public class PersonCreationByLibraryB : IPersonCreationByLibrary
    {
    
        public Person Create()
        {
            // use here libraryB to create a person
            return new Person() { Name = "Person B" };
        }
    }
    

    And this is types of libraries that can be used to create Person:

    public enum Library
    {
        A, 
        B
    }
    

    And this is factory to create strategies of what library should be used to create a Person:

    public class PersonCreationByLibraryFactory
    {
        private Dictionary<Library, IPersonCreationByLibrary> _personByLibrary = 
        new () 
        {
            { Library.A, new PersonCreationByLibraryA() },
            { Library.B, new PersonCreationByLibraryB() }
        };
    
        public IPersonCreationByLibrary GetInstanceByLibrary(Library library) => 
            _personByLibrary[library];
    }
    
        
    

    and this is method which shows how to use the above code:

    Person CreateByLibrary(Library library) 
    {
        PersonCreationByLibraryFactory personCreationByLibraryFactory = 
            new PersonCreationByLibraryFactory();
        IPersonCreationByLibrary personCreationByLibrary =  
            personCreationByLibraryFactory.GetInstanceByLibrary(library);
        return personCreationByLibrary.Create();
    }
    

    And the above code can be run like this:

    Person person = CreateByLibrary(Library.A); // OUTPUT: "Person A"