从基类 (DTO) 继承多个类是不是违反了任何 SOLID 原则?

Posted

技术标签:

【中文标题】从基类 (DTO) 继承多个类是不是违反了任何 SOLID 原则?【英文标题】:Does inheriting multiple class from a base class (DTO) breaks any of the SOLID principles?从基类 (DTO) 继承多个类是否违反了任何 SOLID 原则? 【发布时间】:2017-05-30 02:58:47 【问题描述】:

我从一个基本 DTO 派生多个 DTO(数据传输对象)。我在基础 DTO (isUpdateAvailable) 中有一个属性,该属性在 所有派生类。我有一种方法对于多个用例来说很常见,它采用基本 DTO 并直接使用它或 通过将其转换为相应的派生 DTO。

我认为这不是一个好的 c# 代码设计。应该不需要转换。而且,我还听说这种代码设计违反了一些SOLID原则。

我创建了一个简短的示例代码来描述我的观点。请看:

 public class UpdateNotification
 
    public void ChromeNotification(MyBaseDto baseDto, NotificationType type)
    
        OnUpdateAvailable(baseDto, type);
    
    public void OutlookUpdateNotification(MyBaseDto baseDto,   
  NotificationType type)
    
        OnUpdateAvailable(baseDto, type);
    
    public void OnUpdateAvailable(MyBaseDto baseDto, NotificationType type)
    
        if (type == NotificationType.Chrome)
        
            // it uses baseDto.IsUpdateAvailable as well as it downcast it 
     to DerivedAdto and uses other properties

            var derivedDto = baseDto as DerivedAdto;
        

        if (type == NotificationType.Outlook)
        
            // currently it just uses baseDto.IsUpdateAvailable
        
    
    public enum NotificationType
    
        Chrome,
        Outlook
    
  

我在这里重点介绍 DTO 对象的使用,它们是“MyBaseDto”、“DerivedAdto”和“DeriveddBdto”。我目前的 DTO 结构如下:

  public abstract class MyBaseDto
  
    public MyBaseDto(bool isUpdateAvailable)
    
        IsUpdateAvailable = isUpdateAvailable;
    
    public bool IsUpdateAvailable  get; 
  

  public class DerivedAdto : MyBaseDto
  
    public DerivedAdto(bool isUpdateAvailable)
        : base(isUpdateAvailable)
    

    
    public string PropertyA  get; set; 
  

  public class DerivedBdto : MyBaseDto
  
    public DerivedBdto(bool isUpdateAvailable)
        : base(isUpdateAvailable)
    

    
  

这些 DTO 类有更好的设计吗?

我可以设计类似下面的东西吗?或者你能提出更好的方法吗?

 public abstract class MyBaseDto

   public abstract bool IsUpdateAvailable  get; set;


public class DerivedAdto : MyBaseDto

    public override bool IsUpdateAvailable  get; set;
    public string PropertyA  get; set; 


  public class DerivedBdto : MyBaseDto
 
     public override bool IsUpdateAvailable  get; set;
 

非常感谢。

【问题讨论】:

我投票结束这个问题,因为它更适合 Code Review SE! 【参考方案1】:

继承不违反任何原则。但这确实:

public void OnUpdateAvailable(MyBaseDto baseDto, NotificationType type)

    if (type == NotificationType.Chrome)
    
        // it uses baseDto.IsUpdateAvailable as well as it downcast it 
 to DerivedAdto and uses other properties

        var derivedDto = baseDto as DerivedAdto;
    

    if (type == NotificationType.Outlook)
    
        // currently it just uses baseDto.IsUpdateAvailable
    

如果我必须让它符合其中一个原则,那将是依赖倒置(尽管它有点牵强。)当您采用MyBaseDto 类型的参数时,您依赖于可以表示任何数字的抽象派生类。但是,一旦您开始将其转换回特定的派生类型,您现在就与这些派生类型中的每一个紧密耦合。

当您传递MyBaseDto 类型的参数时,所有方法都应该知道它的类型是MyBaseDto。一旦你开始投射它,你就会开始失去强类型的好处。如果有人传入了错误的NotificationType 并因此尝试将baseDto 转换为错误的类型怎么办?

从技术上讲,您可以将第一个参数的类型更改为object,然后将其转换为您期望的任何类型。但是强类型应该确保你已经知道你有什么类型。而关于一个对象,你只需要知道它的声明类型——参数指定的类型。

这并不违反 Liskov 替换原则,因为这种违反将发生在类 (MyBaseDto) 及其派生类中。问题不在于这些类,而在于它们的使用方式。

简而言之,如果接收类型 A 然后开始检查或强制转换以查看它是否真的是派生类型 BC 则出现问题。我们应该关心的是我们收到的声明类型。

【讨论】:

【参考方案2】:

这里的一些答案可能表明继承对于 DTO 类来说是非常好的,因为它允许您编写通用代码然后在所有派生类中使用它。但是,仅仅因为从 OOP 角度来看in general 是一个好主意,并不意味着从 OOP 角度来看它总是一个好主意,请参阅 [1] 进行解释。

在我看来(根据经验和阅读重量级用户撰写的文章我已经理解)DTO 也属于这一类。我绝对不是说,你不能使用继承,它们只是不能很好地与继承一起工作。您最终会强制添加一些接口以在某些方法中一起使用某些 DTO,例如 IUserDto 以保留用户数据,以及一些其他基类来通用其他 DTO 用法。请参考 [2] 获取支持大约 400 万应用程序下载量的游戏的实际源代码。因此,这是我的另一点:DTO 必须尽可能地自我描述。查看 Object 并查看“DataTransfer”是什么◀(你看到我在这里做了什么吗?)

如果您使用的是 DTO,那么很有可能会有某物/某人会使用该数据,至少我认为是这样。如果不是,您为什么还要传输数据。从现在开始,我将消费者称为“客户”。就像您在UpdateNotification 中提供的代码一样,客户端并不确切知道它具有哪个 DTO,因此请尝试通过强制转换来解决问题,@Scott Hannen 的回答明确指出这是一种代码气味。

关于额外的信息,我们在使用继承的 DTO 开发过程中遇到的一个问题是,当我们使用一些著名的序列化程序从基类序列化时,它们未能(通过设计)序列化以包含派生属性,或者当我们试图反序列化为公共接口或基类,它们未能包含派生属性或完全失败。一些序列化器包含type信息来解决这个问题,请参阅[3]。

[1] 例如,我在一家主要制作 RPG 游戏的公司工作。假设我们尝试通过以下路由GameEntity→Movable→Monster→FireMonster→Dragon 使用继承为怪物创建一个具体类。如果计划在开发过程中想要更改Dragon 上的某些参数怎么办?继承自FireMonsterFireGolem 怎么样?我们在开发的后期添加一些FlyingMonster 怎么样。它将在继承表中的什么位置? FireGolem 会受到影响吗? - 它可能会受到影响 - 这就是为什么在游戏行业我们总是更喜欢组合而不是继承。我也不是说我们也不使用继承。

[2] 请不要感到震惊 :) 它们也有部分类,这些类是使用自动创建工具生成的。我敢于调试或尝试了解它们之间的层次结构。哎呀尝试更改 IEntity 或其他类中的单个属性,然后您将不得不修复其他 50 个其他类。

public interface IDto : IEntity, ICloneable, IHasId<long>

public abstract class DtoBase<T> : IDto where T : class, IDto, new()

public abstract class UserDtoBase<T> : DtoBase<T> where T : class, IDto, new()

public partial class ProfileDto : UserDtoBase<ProfileDto>

[3] 在尝试了几个序列化器之后,ServiceStack 是唯一一个正确地反序列化/序列化到/从接口/派生/基本类型的序列化器。我不知道序列化程序的当前情况。

【讨论】:

我会很感激 -1 的理由 “DTO 必须尽可能自我描述”是什么意思?为什么继承是一个坏主意? OP 的代码中哪里有关于“客户端”或“服务”的内容?为什么没有任何好的理由来拥有接口/抽象基类? “抽象基础也是要遵守的契约”是什么意思? DTO 类将如何无法正确反序列化? “正确”是指他们会不正确地反序列化吗?那是什么意思? @Enigmativity,我不明白你为什么对一个问题如此生气。我也会更新我的答案来回答你的问题,但你的一些问题非常愚蠢。What does "DTO has to be as self-describing as possible" mean?◀这里有什么不明白的地方? @Enigmativity,你真的是说我没有回答吗?我想你是故意这样说的。第二段清楚地解释了“DTO 必须尽可能自我描述”是什么意思?另一个,“OP 的代码中哪里有关于“客户端”或“服务”的内容?我也解释了这个请看客户消费部分? “DTO 类如何无法正确反序列化?”而“正确”是指它们将不正确地反序列化吗?” 看额外的信息?!哪些难以理解,我将为您简化它. @Enigmativity,我从没想过会在 *** 上看到一些互联网巨魔。 没有理解我的意思。有什么不明白的是,如果你使用继承的 DTO,一些最常用的序列化程序会失败。我将其作为额外信息提供,假设由于 OP 正在处理 DTO,在某个时候或到目前为止,他/她也在处理反序列化/序列化,如果您使用 DTO,这很常见。所以如果你使用继承的 DTO,有(是)高风险,他会面临一些问题。请不要进一步回答,因为我没心情喂巨魔【参考方案3】:

就 DTO 类的继承而言,您的设计非常好。这正是继承的用途——允许您在基类中编写通用代码,然后在所有派生类中使用它。

奇怪的是 - 我不完全确定你想要做的是从 ChromeNotificationOutlookUpdateNotification 调用 OnUpdateAvailable。在这两种情况下,您都传递了NotificationType,它似乎提供了与调用这两种方法时相同的信息。

在我看来,您的UpdateNotification 设计得不好。不过,您的 DTO 对象看起来非常好。

【讨论】:

我真的不明白为什么这会导致投票失败。我将不胜感激。

以上是关于从基类 (DTO) 继承多个类是不是违反了任何 SOLID 原则?的主要内容,如果未能解决你的问题,请参考以下文章

从基类访问子类成员的首选方式

错误:与 'operator=' 不匹配。试图从基类继承并从基类初始化?

获取使用StructureMap从基类继承的唯一类型实例

派生类不从基类继承重载方法

从基类创建继承类

C++ - 让派生类从基类“继承”重载赋值运算符的安全/标准方法