从基类 (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
然后开始检查或强制转换以查看它是否真的是派生类型 B
或 C
则出现问题。我们应该关心的是我们收到的声明类型。
【讨论】:
【参考方案2】:这里的一些答案可能表明继承对于 DTO 类来说是非常好的,因为它允许您编写通用代码然后在所有派生类中使用它。但是,仅仅因为从 OOP 角度来看in general
是一个好主意,并不意味着从 OOP 角度来看它总是一个好主意,请参阅 [1] 进行解释。
在我看来(根据经验和阅读重量级用户撰写的文章我已经理解)DTO 也属于这一类。我绝对不是说,你不能使用继承,它们只是不能很好地与继承一起工作。您最终会强制添加一些接口以在某些方法中一起使用某些 DTO,例如 IUserDto
以保留用户数据,以及一些其他基类来通用其他 DTO 用法。请参考 [2] 获取支持大约 400 万应用程序下载量的游戏的实际源代码。因此,这是我的另一点:DTO 必须尽可能地自我描述。查看 Object 并查看“Data它Transfer”是什么◀(你看到我在这里做了什么吗?)
如果您使用的是 DTO,那么很有可能会有某物/某人会使用该数据,至少我认为是这样。如果不是,您为什么还要传输数据。从现在开始,我将消费者称为“客户”。就像您在UpdateNotification
中提供的代码一样,客户端并不确切知道它具有哪个 DTO,因此请尝试通过强制转换来解决问题,@Scott Hannen 的回答明确指出这是一种代码气味。
关于额外的信息,我们在使用继承的 DTO 开发过程中遇到的一个问题是,当我们使用一些著名的序列化程序从基类序列化时,它们未能(通过设计)序列化以包含派生属性,或者当我们试图反序列化为公共接口或基类,它们未能包含派生属性或完全失败。一些序列化器包含type
信息来解决这个问题,请参阅[3]。
[1] 例如,我在一家主要制作 RPG 游戏的公司工作。假设我们尝试通过以下路由GameEntity→Movable→Monster→FireMonster→Dragon
使用继承为怪物创建一个具体类。如果计划在开发过程中想要更改Dragon
上的某些参数怎么办?继承自FireMonster
的FireGolem
怎么样?我们在开发的后期添加一些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 类的继承而言,您的设计非常好。这正是继承的用途——允许您在基类中编写通用代码,然后在所有派生类中使用它。
奇怪的是 - 我不完全确定你想要做的是从 ChromeNotification
和 OutlookUpdateNotification
调用 OnUpdateAvailable
。在这两种情况下,您都传递了NotificationType
,它似乎提供了与调用这两种方法时相同的信息。
在我看来,您的UpdateNotification
设计得不好。不过,您的 DTO 对象看起来非常好。
【讨论】:
我真的不明白为什么这会导致投票失败。我将不胜感激。以上是关于从基类 (DTO) 继承多个类是不是违反了任何 SOLID 原则?的主要内容,如果未能解决你的问题,请参考以下文章