微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

设计通用产品处理器

如何解决设计通用产品处理器

我正在尝试将一些订单放入管理来自多个来源的产品的大型应用程序中,所有这些产品彼此不同但映射到一个表中(需要的值)。

我希望听到意见、经验、模式或任何类型的建议。

假设我们将 Product_A、Product_B、Product_C 都映射到“Product_Dto”并作为 Product 保存到 sql 中(现在不同的方法、不同的类和文件

/// <summary>
/// INACCESIBLE TO DEVELOPERS
/// </summary>
public class Product
{
    public int id { get; set; }
    public string sku { get; set; }
    public string name { get; set; }
    public int status_id { get; set; }
    public int category_id { get; set; }
    public string description { get; set; }
    public decimal price { get; set; }
    public List<int> quantity { get; set; }
}

public class Product_Dto
{
    public int id { get; set; }
    public string sku { get; set; }
    public string name { get; set; }
    public int? status_id { get; set; }
    public int category_id { get; set; }
    public string description { get; set; }
    public decimal price { get; set; }
}

主要思想是重新组织(重写)代码并提供一个抽象类,每次需要添加新的 Product 源时都必须实现该抽象类。

为什么是抽象类?一些要点

  • 我们想隐藏适用于所有来源的功能(私有方法
  • 我们希望提供开发人员在实现新源(公共或受保护方法)时需要的功能
  • 我们希望确保开发人员编写一些必要的代码(抽象方法
  • 我们希望提供在某些场景中实现自定义代码的可能性(虚拟方法

正如我提到的,应用程序管理来自多个来源的产品,并且某些信息必须通过执行多个没有人可以触及、覆盖等的私有方法进行处理、格式化和映射 (通过查找其他列表和来源进行业务逻辑和值分配),这是我发现的最大问题。

恢复:

  • 创建抽象类:ok
  • 添加私有、公共和抽象方法:OK
  • 将 Products_A 映射到 Product_D 以手动应用逻辑:OK => GetProduct_Dto(Product_A product)
  • 将 Products_B 映射到 Product_D 以手动应用逻辑:OK => GetProduct_Dto(Product_B product)
  • 创建通用映射器:NOT OK => GetProduct_Dto(Product_Generic product);

如果为具有真实产品类型的每个案例编写自定义 Dto,请采用这种方式!

好吧,但出于某种原因我不喜欢它。

我们正在讨论为每个来源映射至少 60 个属性。 90% 的属性具有与原始(状态、类型、类别等)不同的数据库值,因此该值来自私有函数(对于所有场景都相同)。 此外,产品类 (DB) 经常更改,并且必须将属性添加到每个源/映射中,并确保将正确的功能用于新的功能,没有接口是不可能的。 (只有 CREATE、x2 FOR UPDATE 具有相同的属性/功能,但某些插入值(如 created_date、sku)除外,这些值永远不会改变)。

解决方法 => 通用映射器:接口方式

好的,为了创建一个通用的 Mapper,我们可以实现一个接口,我们称之为 IProduct,Product_A、Product_B 和 Product_C 必须实现它。然后,我们可以写一个新的GetProduct_Dto

public interface IProduct
{
    //INTERFACES CANNOT CONTAIN INSTANCE CONSTRUCTORS (to assign source on new)
    //public IProduct(Product_Source_Enum source)
    //{
    //    this.source = source;
    //}

    public Product_Source_Enum source { get; set; }
    public int id { get; set; }
    public string sku { get; set; }
    public string name { get; set; }
    public string status { get; set; }
    public string category { get; set; }
    public string description { get; set; }
    public decimal price { get; set; }

    public enum Product_Source_Enum
    {
        A,B,C
    }
}

public class Product_A : IProduct
{
    public string id { get; set; }
    public string sku { get; set; }
    public string title { get; set; }
    public ProductA_Status_Enum status { get; set; }
    public string category_name { get; set; }
    public string description_text { get; set; }
    public decimal price { get; set; }
    public int quantity { get; set; }


    public IProduct.Product_Source_Enum source { get; set; } = IProduct.Product_Source_Enum.A;
    int IProduct.id { get { if (int.TryParse(this.id,out int ID)) { return ID; } return 0; } set => this.id = value.ToString(); }
    string IProduct.name { get => this.title; set => this.title = value; }
    string IProduct.status { get => this.status.ToString(); set { if (Enum.TryParse<ProductA_Status_Enum>(value,out ProductA_Status_Enum STATUS)) { this.status = STATUS; } } }
    string IProduct.category { get => this.category_name; set => this.category_name = value; }
    string IProduct.description { get => this.description_text; set => this.description_text = value; }


    public enum ProductA_Status_Enum
    {
        active,inactive
    }
}

public class Product_B : IProduct
{
    public int id { get; set; }
    public string sku { get; set; }
    public string name { get; set; }
    public string description { get; set; }
    public ProductB_Status_Enum general_status { get; set; }
    public string category { get; set; }
    public decimal price { get; set; }
    public int quantity { get; set; }


    public IProduct.Product_Source_Enum source { get; set; } = IProduct.Product_Source_Enum.B;
    int IProduct.id { get => this.id; set => this.id = value; }
    string IProduct.name { get => this.name; set => this.name = value; }
    string IProduct.status { get => this.general_status.ToString(); set { if (Enum.TryParse<ProductB_Status_Enum>(value,out ProductB_Status_Enum STATUS)) { this.general_status = STATUS; } } }
    string IProduct.category { get => this.category; set => this.category = value; }
    string IProduct.description { get => this.description; set => this.description = value; }
    decimal IProduct.price { get => this.price; set => this.price = value; }


    public enum ProductB_Status_Enum
    {
        enabled,disabled,paused
    }
}
  1. 创建一个 IProduct 接口:好的
  2. 在 Product_A、Product_B 和 Product_C 中实现 IProduct:好的
  3. 编写 GetProduct_Dto(IProduct product):好的,但需要转换

将接口 IProduct 映射到 Product_Dto 有什么问题?

我需要将所有源的所有值合并到接口中以维护当前功能并将这些值映射到 Product_Dto 内部(私有映射)*

  • FALSE => 从 IProduct 转换到 Product_A、Product_B、Product_C 将解决问题。

简历

  • 实现 IProduct 接口非常适合填充 Product_Dto,因为只包含基本所需的属性,从而保持接口较小。

     /// <summary>
     /// Private Mapping between Interface object and Product_Dto
     /// </summary>
     /// <param name="product"></param>
     /// <returns></returns>
     private Product_Dto GetProduct_Dto(IProduct product)
     {
         return new Product_Dto
         {
             category_id = GetCategory(product.category),description = product.description,id = product.id,name = product.name,price = product.price,status_id = GetStatus(product.status)
         };
     }
    

完整的抽象类

public abstract class ProductProcessor
{

    private Database _db = new Database();

    /// <summary>
    /// Private Mapping between Interface object and Product_Dto
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    private Product_Dto GetProduct_Dto(IProduct product)
    {
        return new Product_Dto
        {
            category_id = GetCategory(product.category),status_id = GetStatus(product.status)
        };
    }

    protected Product_Dto Create(IProduct product)
    {
        var productDto = GetProduct_Dto(product);
        var productDb = new Product();
        _db.Product.Add(productDb);
        _db.SetValues(productDb,productDto);

        //private method,different logics for every type but still PRIVATE
        //DEVELOPERS CAN'T DECIDE WAT HAPPEN HERE
        Create_Depot_With_Stock(productDb,product); //=> CAST WILL BE NEEDED TO ACCESS FULL OBJECT

        //Create function has a lot of private functions,casting will be needed for each function

        //Other_1(productDb,product); //=> CAST WILL BE NEEDED TO ACCESS FULL OBJECT =====> EXCESIVE CASTING???
        //Other_2(productDb,product); //=> CAST WILL BE NEEDED TO ACCESS FULL OBJECT =====> EXCESIVE CASTING???
        //Other_3(productDb,product); //=> CAST WILL BE NEEDED TO ACCESS FULL OBJECT =====> EXCESIVE CASTING???

        _db.SaveChanges();
        return productDto;
    }

    public Product_Dto Update(IProduct product)
    {
        var productDb = _db.Find(product.sku);
        var productDto = GetProduct_Dto(product);
        _db.SetValues(productDb,different logics for every type but still PRIVATE
        //DEVELOPERS CAN'T DECIDE WAT HAPPEN HERE
        Update_Source_Quantity(product,productDb); //=> CAST WILL BE NEEDED TO ACCESS FULL OBJECT

        //Other_1(productDb,product); //=> CAST WILL BE NEEDED TO ACCESS FULL OBJECT =====> EXCESIVE CASTING???


        Update_Source_Quantity_WORKAROUND(product); //MUST BE OVERRIDED AND CASTED BY DEVELOPERS

        _db.SaveChanges();
        return productDto;
    }

    /// <summary>
    /// Private method,developer must implement the interface properties and here we process the values
    /// </summary>
    /// <param name="status"></param>
    /// <returns></returns>
    private int GetCategory(string status)
    {
        var statuses = new Dictionary<string,int>
        {
            { "none",0 },{ "CatA",1 },{ "CatB",2},{ "CatC",3 }
        };

        if (statuses.ContainsKey(status))
            return statuses.GetValueOrDefault(status);

        return 0;
    }

    private int? GetStatus(string status)
    {
        var statuses = new Dictionary<string,int> {
            { "other",{ "active",{ "inactive",{ "enabled",3 },{ "disabled",4 },{ "paused",5 } };

        if (statuses.ContainsKey(status))
            return statuses.GetValueOrDefault(status);

        return null;
    }

    /// <summary>
    /// EVERY METHOD THAT NEED THE FULL PRODUCT WILL NEED TO CAST THE IProduct
    /// </summary>
    /// <param name="productDb"></param>
    /// <param name="product"></param>
    private void Create_Depot_With_Stock(Product productDb,IProduct product)
    {
        int product_stock = 0;// product.quantity doesnt exists in interface WE MUST CAST TYPES

        if (product.source == IProduct.Product_Source_Enum.A)
        {
            var product_a = (Product_A)product;
            product_stock = product_a.quantity;
            productDb.quantity = new List<int> { product_stock };
            return;
        }

        if (product.source == IProduct.Product_Source_Enum.B)
        {
            var product_a = (Product_A)product;
            product_stock = product_a.quantity;
            productDb.quantity = new List<int> { product_stock };
            return;
        }
    }

    /// <summary>
    /// OUTSIDE DEVELOPERS DONT NEED TO KNow THIS IS HAPPENING
    /// </summary>
    /// <param name="product"></param>
    /// <param name="productDb"></param>
    private void Update_Source_Quantity(IProduct product,Product productDb)
    {
        if (product is Product_A)
        {
            var product_a = (Product_A)product;
            //THIS IF IS THE SAME FOR EVERY Product Type,EXTRACT FUNCTION AND USE VALUES?
            if (product_a.quantity != productDb.quantity.Sum())
            {
                //do something
            }
            return;
        }

        if (product is Product_B)
        {
            var product_b = (Product_B)product;
            //THIS IF IS THE SAME FOR EVERY Product Type,EXTRACT FUNCTION AND USE VALUES?
            if (product_b.quantity != productDb.quantity.Sum())
            {
                //do something
            }
            return;
        }
    }

    protected abstract void Update_Source_Quantity_WORKAROUND(IProduct product);

}
  • 我不喜欢什么:当需要为一个属性(通过私有函数)查找数据库值时,该函数将接受一个字符串通用参数作为状态而不是枚举(但没有人会死)。

     private int? GetStatus(string status)
     {
         var statuses = new Dictionary<string,int> {
             { "other",5 } };
    
         if (statuses.ContainsKey(status))
             return statuses.GetValueOrDefault(status);
    
         return null;
     }
    
  • 我找不到一种简单的方法将完整的 Product_A、B 和 C 运送到基地,以便以正确的类型管理它们(这会使一切变得更容易)例如。 Product.status = Product_A.Status_Enum.active 而不是字符串中的状态。

  • FALSE AGAIN => 从 IProduct 转换到 Product_A、Product_B、Product_C 将解决问题。

      /// <summary>
      /// EVERY METHOD THAT NEED THE FULL PRODUCT WILL NEED TO CAST THE IProduct
      /// </summary>
      /// <param name="productDb"></param>
      /// <param name="product"></param>
      private void Create_Depot_With_Stock(Product productDb,IProduct product)
      {
          int product_stock = 0;// product.quantity doesn't exists in interface WE MUST CAST TYPES
    
          if (product.source == IProduct.Product_Source_Enum.A)
          {
              var product_a = (Product_A)product;
              product_stock = product_a.quantity;
              productDb.quantity = new List<int> { product_stock };
              return;
          }
    
          if (product.source == IProduct.Product_Source_Enum.B)
          {
              var product_a = (Product_A)product;
              product_stock = product_a.quantity;
              productDb.quantity = new List<int> { product_stock };
              return;
          }
      }
    

一些未解决的问题:

  1. 我使用 source 属性只是为了将 Product 转换为其原始类型。由于接口不能包含实例构造函数,因此分配源在开发人员手中。 有什么解决方法吗?

     //public IProduct(Product_Source_Enum source)
     //{
     //    this.source = source;
     //}
    

AUTO RESPONSE 1:将接口转换为抽象类可能会起作用。有什么想法吗?

  1. 每次我需要具体类时,如何转换接口?每种方法都有很多 IFS 仅用于铸造目的。 任何想法如何处理?

     /// <summary>
     /// EVERY METHOD THAT NEED THE FULL PRODUCT WILL NEED TO CAST THE IProduct
     /// </summary>
     /// <param name="productDb"></param>
     /// <param name="product"></param>
     private void Create_Depot_With_Stock(Product productDb,IProduct product)
     {
         int product_stock = 0;// product.quantity doesnt exists in interface WE MUST CAST TYPES
    
         if (product.source == IProduct.Product_Source_Enum.A)
         {
             var product_a = (Product_A)product;
             product_stock = product_a.quantity;
             productDb.quantity = new List<int> { product_stock };
             return;
         }
    
         if (product.source == IProduct.Product_Source_Enum.B)
         {
             var product_a = (Product_A)product;
             product_stock = product_a.quantity;
             productDb.quantity = new List<int> { product_stock };
             return;
         }
     }
    
     /// <summary>
     /// OUTSIDE DEVELOPERS DONT NEED TO KNow THIS IS HAPPENING
     /// </summary>
     /// <param name="product"></param>
     /// <param name="productDb"></param>
     private void Update_Source_Quantity(IProduct product,Product productDb)
     {
         if (product is Product_A)
         {
             var product_a = (Product_A)product;
             //THIS IF IS THE SAME FOR EVERY Product Type,EXTRACT FUNCTION AND USE VALUES?
             if (product_a.quantity != productDb.quantity.Sum())
             {
                 //do something
             }
             return;
         }
    
         if (product is Product_B)
         {
             var product_b = (Product_B)product;
             //THIS IF IS THE SAME FOR EVERY Product Type,EXTRACT FUNCTION AND USE VALUES?
             if (product_b.quantity != productDb.quantity.Sum())
             {
                 //do something
             }
             return;
         }
     }
    

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。