Какой шаблон проектирования использовать, когда у нас есть классы, которые обладают аналогичной функциональностью высокого уровня, но возвращают разные типы в методах? - PullRequest
0 голосов
/ 28 мая 2020

У меня есть существующее консольное приложение C#, которое принимает аргументы и на основе аргументов создает экземпляр рынков (Великобритания, США, MX ..), используя внедрение зависимостей.

Каждый рыночный класс выполняет 'string GetData ()', 'string ProcessData ()' и 'bool ExportData ()'.

Приложение было изначально создано для рынков одного поставщика электронной коммерции. Теперь мне сказали изменить его для другого поставщика, который выполняет другой процесс. Поток высокого уровня остается прежним.

  1. 'GetData' для выборки записей из БД,
  2. 'ProcessData' для любого преобразования или подобного
  3. 'ExportData'.

Разница в том, что Getdata () извлекает записи из БД и сопоставляет их с объектом. Я планирую использовать Petapoco. «ProcessData» может возвращать аналогичный класс. «Exportdata» в настоящее время выполняет вызов API, но для нового поставщика я должен записать в файл.

Я читал о шаблонах, я полностью запутался. Сначала я думал, что мне нужен абстрактный фабричный шаблон, а теперь я думаю, что фабричный метод - это то, что я должен использовать, но я не уверен, правильно ли я делаю это. Здесь нужны рекомендации / обзор. Пример файла cs, который я создал на основе моего понимания фабричного шаблона. Этот код основан на примерах кода headfirst.

using System;
using System.Collections.Generic;
using StatusExport.Models;

namespace factorymethod
{
    class Program
    {
        static void Main(string[] args)
        {
            ClientFactory factory = null;
            Console.WriteLine("Enter client code:");
            string clientCode= Console.ReadLine();
            switch (clientCode.ToLower())
            {
                case "costco":
                    factory = new CostcoFactory("accountname", "taskname");
                    break;
                    //NEw vendor might be added
                    //case "walmart"
                    //factory = new WalmartFactory("taskname", "type");
                    //break
                default:
                    break;
            }

            bool status = factory.ProcessData();
            Console.ReadKey();
        }
    }

    abstract class Client
    {
        public abstract string AccountName { get; }
        public abstract string Task { get; set; }
        //More properties might be added. Some may not even be used by some of the new vendors. For example, Costco Might need accountname and task. Tomorrow if walmart comes, they might not need these two or may need task and a new property 'type'
        public abstract List<T> GetData<T>();
        public abstract List<T> ProcessData<T>();
        public abstract bool ExportData();
    }


    class CostcoClient : Client
    {
        public override string AccountName { get; }
        public override string Task { get; set; }

        public CostcoClient(string accountName, string task)
        {
            AccountName = accountName;
            Task = task;
        }

        public override List<DBRecord> GetData<DBRecord>() //DBRecord class is specific to Costco. 
        {
            List<DBRecord> dbresult = new List<DBRecord>();
            //dbresult = db return data mapped to an object DBRecord using petapoco. Another vendor might have a different class to which DB records are mapped. So the return type can be generic
            return asn;
        }

        public override List<T> ProcessData<T>()
        {
            throw new NotImplementedException(); //Any data transformation or business logic. Return type might be DBRecord or a new class altogether
        }

        public override bool ExportData()
        {
            throw new NotImplementedException();//Call API or write data to file and if success send true else false
        }
    }

    abstract class ClientFactory
    {
        public abstract bool ProcessData();
    }


    class CostcoFactory : ClientFactory
    {
        public string AccountName { get; }
        public string Task { get; set; }
        public CostcoFactory(string accountname, string task)
        {
            AccountName = accountname;
            Task = task;
        }

        public override bool ProcessData()
        {
            CostcoClient gc = new CostcoClient(AccountName, Task);
            var result = gc.GetData<DBRecord>();
            return true;
        }
    }
}

Как вы думаете, это правильный подход к дизайну?

Я также хочу, чтобы проект консоли не зависел от проекта поставщика. Так что, возможно, «StatusExport.Program» для консольного приложения. DLL проекты StatusExport.Common для хранения интерфейса и абстрактных классов и StatusExport.Client (например, StatusExport.Costco) для каждого материала поставщика.

1 Ответ

1 голос
/ 30 мая 2020

Вы можете создать класс BaseClient, который будет содержать базовую c группу свойств, и если вам нужно добавить что-то новое - просто унаследуйте его. Вы все сделали правильно, но я думаю, что лучше изменить модификатор publi c на protected в ваших свойствах AccountName и Task , чтобы предоставлять к ним доступ только из дочерних классов. Собственно, вы можете создать BaseClientModels (запрос / ответ) для каждого метода, если не уверены, что возвращаемый тип List всегда будет актуальным. Пример:

public abstract class BaseClient
{
    #region Properties : Protected

    protected abstract string AccountName { get; }

    protected abstract string Task { get; set; }

    #endregion

    #region Methods : Public

    public abstract BaseGetDataResponseModel GetData(BaseGetDataRequestModel model);

    public abstract BaseProcessDataResponseModel ProcessData(BaseProcessDataRequestModel model);

    public abstract BaseExportDataResponseModel ExportData(BaseExportDataRequestModel model);

    #endregion
}

public class BaseGetDataResponseModel { }

public class BaseGetDataRequestModel { }

public class BaseProcessDataResponseModel { }

public class BaseProcessDataRequestModel { }

public class BaseExportDataResponseModel { }

public class BaseExportDataRequestModel { }

Затем давайте посмотрим на ваш класс CostcoClient и как он может выглядеть:

public class CostcoClient : BaseClient
{
    #region Properties : Protected

    protected override string AccountName { get; }

    protected override string Task { get; set; }

    protected virtual IDataReader<BaseGetDataRequestModel, BaseGetDataResponseModel> DataReader { get; }

    protected virtual IDataProcessor<CostcoClientProcessDataRequestModel, CostcoClientProcessDataResponseModel> DataProcessor { get; }

    protected virtual IExportDataHandler<CostcoClientExportDataRequestModel, CostcoClientExportDataResponseModel> ExportDataHandler { get; }

    #endregion

    #region Constructors

    public CostcoClient(string accountName, string task)
    {
//set DataReader, DataProcessor, ExportDataHandler
        AccountName = accountName;
        Task = task;
    }

    #endregion

    #region Methods : Public

    public override BaseGetDataResponseModel GetData(BaseGetDataRequestModel model)
    {
        if (model is CostcoClientGetDataRequestModel clientGetDataRequestModel)
        {
            return DataReader.ReadData(clientGetDataRequestModel);
        }
        return null; //wrong type has passed
    }

    public override BaseProcessDataResponseModel ProcessData(BaseProcessDataRequestModel model)
    {
        if (model is CostcoClientProcessDataRequestModel clientProcessDataRequestModel)
        {
            return DataProcessor.ProcessData(clientProcessDataRequestModel);
        }
        return null;
    }

    public override BaseExportDataResponseModel ExportData(BaseExportDataRequestModel model)
    {
        if (model is CostcoClientExportDataRequestModel clientExportDataRequestModel)
        {
            return ExportDataHandler.Handle(clientExportDataRequestModel);
        }
        return null;
    }

    #endregion
}


public class CostcoClientGetDataRequestModel : BaseGetDataRequestModel { }

public class CostcoClientGetDataResponseModel : BaseGetDataResponseModel { }

public class CostcoClientProcessDataRequestModel : BaseProcessDataRequestModel { }

public class CostcoClientProcessDataResponseModel : BaseProcessDataResponseModel { }

public class CostcoClientExportDataRequestModel : BaseExportDataRequestModel { }

public class CostcoClientExportDataResponseModel : BaseExportDataResponseModel { }

public interface IDataReader<TIn, TOut>
{
    public TOut ReadData(TIn model);
}

public interface IDataProcessor<TIn, TOut>
{
    public TOut ProcessData(TIn model);
}

public interface IExportDataHandler<TIn, TOut>
{
    public TOut Handle(TIn model);
}

public class CostcoClientDataReader : IDataReader<CostcoClientGetDataRequestModel, CostcoClientGetDataResponseModel>
{
    public CostcoClientGetDataResponseModel ReadData(CostcoClientGetDataRequestModel model)
    {
        throw new NotImplementedException();
    }
}

    //and so on

Вы должны реализовать IDataReader , IDataProcessor , IExportDataHandler , сделайте ваш лог c и вызовите его из GetData , ProcessData , ExportData методы, как пример, и получить экземпляры через внедрение зависимостей.

Затем мы можем изменить ваш завод на это:

public interface IClientFactory
{
    BaseClient GetClientService(ClientServicesEnum value);
}


public class BaseClientFactory : IClientFactory
{
    #region Propertied : Protected

    protected virtual IEnumerable<BaseClient> Services { get; }

    protected string AccountName { get; }

    protected string Task { get; set; }

    #endregion

    #region Constructors

    public BaseClientFactory(IEnumerable<BaseClient> services, string accountname, string task)
    {
        Services = services;
        AccountName = accountname;
        Task = task;
    }

    #endregion

    public BaseClient GetClientService(ClientServicesEnum value)
        => Services.First(x => x.GetType().Equals(GetClientServiceByCode()[value]));

    private Dictionary<ClientServicesEnum, Type> GetClientServiceByCode()
        => new Dictionary<ClientServicesEnum, Type>()
        {
            { ClientServicesEnum.CostcoClient, typeof(CostcoClient) }
        };

}

public enum ClientServicesEnum
{
    CostcoClient = 1,
    Another2 = 2,
    Another3 = 3
}

Где

protected virtual IEnumerable<BaseClient> Services { get; }

вы также можно получить через DI, а затем получить правильный ServiceHandler с помощью enum.

И ваша основная функция для вызова всего этого:

        switch (clientCode)
        {
            case 1:
                baseClient = ClientFactory.GetClientService(ClientServicesEnum.CostcoClient);
                break;
            case 2:
                baseClient = ClientFactory.GetClientService(ClientServicesEnum.Another2);
                break;
            default:
                break;
        }

        bool status = baseClient.ProcessData(null); //your model

Главное - вы можете использовать более одного шаблона , например один из Creational patterns и один из Structural. Если мне нужна помощь в архитектуре кода, я использую это: https://refactoring.guru/

Я думаю, с помощью этого примера вы можете удалить свойства AccountName и Task , из-за моделей запросов в методах.

...