替换继承中的对象
本文关键字:对象 继承 替换 | 更新日期: 2023-09-27 18:23:41
我有一个同时包含基元和自定义属性的类:
public class Version_1
{
public string Name { get; set; }
public int Age { get; set; }
public WeirdDad Weird { get; set; }
public MysteriousDad Mysterious { get; set; }
}
将来,我想用int属性加上我的一个自定义对象的自定义来扩展这个类,方法是:
public class Version_2 : Version_1
{
public string IdentityCode { get; set; }
public WeirdChild Weird { get; set; }
}
在Version_2类中,WeirdDad对象的外观已替换为其子对象WeirdChild,所以我想替换它。在本例中,我将在Version_2类中同时具有WeirdDad和WeirdChild。
您将如何实现此示例?
我相信您想要做的是方法隐藏。Version_2类应该如下所示:
public class Version_2 : Version_1
{
public string IdentityCode { get; set; }
public new WeirdChild Weird { get; set; }
}
当您想从Version_1访问"Weird"属性时,您必须调用base.Weird
但请注意,这不是推荐的,而且是一种代码气味。
IMHO,Version_2不应继承Version_1,因为它的行为不能完全像Version_1:
Version_2 version_2Instance = new Version_2();
version_2Instance.Weird = new WeirdDad();
永远不要忘记,继承假定您是在扩展基类,而不是修改它
如何使用模板类和基础抽象类:
public abstract class VersionBase<T>
{
public string Name { get; set; }
public int Age { get; set; }
public abstract T Weird { get; set; }
public MysteriousDad Mysterious { get; set; }
}
然后:
public class Version_1 : VersionBase<WeirdDad>
{
public override WeirdDad Weird { get; set; }
}
public class Version_2 : VersionBase<WeirdChild>
{
public string IdentityCode { get; set; }
public override WeirdChild Weird { get; set; }
}
思考遗传+多态性
下面是一些代码,我将在。。。
public abstract class WeirdPerson() {
public virtual void DoThis() {
// base / default implementation as desired
}
public virtual void DoThat() { // ditto }
public abstract void GoWildAndCrazy;
}
public class WeirdChild : WeirdPerson {
public override void DoThis() { // weird child behavior}
// etc.
}
public class WeirdDad : WeirdPerson {
// override and add methods as needed.
}
public class Version_1
{
public string Name { get; protected set; }
public int Age { get; protected set; }
public WeirdPerson WeirdFamilyMember { get; protected set; }
public MysteriousDad Mysterious { get; protected set; }
public Version_1 (WeirdChild child, string name, int age, MysteriousDad Dad) {
}
}
public class Version_2 : Version_1 {
public Version_2 (WeirdDad dad, string name, int age, MysteriousDad grandfather) : base (dad, name, age, grandfather) {}
}
WeirdPerson
——一个更通用的概念,定义了所有子类型的基本内容。- 子类型将获得"固定"的东西,如
Name
-继承 - 子类型可以
override
默认行为(方法)-多态性 - 现在我们没有好看的东西了:
public class WeirdDad : WeirdChild
- 子类型将获得"固定"的东西,如
- 构造函数
Version_1
、Version_2
构造函数强制客户端提供适当的WeirdPerson
子类型- 客户必须给我们所有需要的东西
- 我们可以验证/交叉验证传入的参数
- 我们不需要臭
interface
abstract
类允许我们拥有默认行为(方法)和默认状态(属性)- 任何类的
public
方法和属性都是一般意义上的接口。我们不需要有(C#关键字)interface
,以便遵循代码到接口而非实现的原则 abstract
方法强制实现子类。只是喜欢和.interface
new
- 警告。方法隐藏会在该点切断继承链。如果我们从这个类继承,我们就不会继承原始基类的方法实现
- 利斯科夫很高兴
附加重构
使Version
继承权与WeirdPerson
继承权平行
假设这符合您的设计。我想,一年后你会很高兴你做到了。
public abstract class Version_X {
// all the original properties here.
// virtual and abstract methods as needed
// LOOK! standard-issue constructor!
protected Version_X ( WeirdPerson person, ...) { // common validation, etc. }
}
public class Version_1 : Version_X {
public Version_1( WeirdChild child, ... ) : base ( child, ... ) {}
}
public class Version_2 : Version_X {
public Version_2 ( WeirdDad dad, ... ) {}
}
编辑-受到与DVK的评论讨论的激励
最少知识原则说,客户不应该知道内部细节才能使用类。有人可能会争辩说,需要知道如何组成正确的Version
和Weird
是一种违规行为。
假设Visitor
的默认构造函数在整个设计的其他地方是必要的。允许这意味着客户端可以控制Version
/Weird
的组成。
抽象-松散耦合-在abstract
类中。具体类必须正确组合,因此在创建具体对象(显式类型构造函数参数)时,"强耦合"是固有的,但底层的松耦合允许所需的灵活性。
public enum WeirdFamily { Child, Dad, Mother, TheThing }
public static class AdamsFamilyFactory () {
public static Version_X Create (WeirdFamily familyMember) {
switch (familyMember) {
case Dad:
return BuildAdamsDad();
// . . .
}
}
}
public static class MunstersFactory() { // Munsters implementation }
// client code
List<Version_X> AdamsFamily = new List<Version_X>();
Version_X Pugsly = AdamsFamilyFactory.Create(WeirdFamily.Child);
AdamsFamily.Add(Pugsly);
List<Version_X> Munsters= new List<Version_X>();
Version_X Eddie= MunstersFactory.Create(WeirdFamily.Child);
Munsters.Add(Eddie);
DoTheMonsterMash(Munsters);
DoTheMonsterMash(AdamsFamily);
public void DoTheMonsterMash(List<Version_X> someWeirdFamily {
foreach (var member in someWeirdFamily)
member.GoWildAndCrazy();
}
您需要重新思考您试图做什么,因为您的提议打破了Liskov替换原则。
发件人:https://en.wikipedia.org/wiki/Liskov_substitution_principle
可替换性是面向对象编程的一个原则。它指出,在计算机程序中,如果S是T的子类型,则T类型的对象可以用S类型的对象替换(即,S类型的物体可以替换T类型的物体),而不改变该程序的任何期望属性(正确性、执行的任务等)
如果您只希望Version_2包含一个WeirdChild,而不希望任何类型的类实现WeirdDad,那么这是可以实现的,但您需要备份一个级别,并开始从泛型、接口和/或抽象类的角度进行思考。
例如:
public interface IWeirdFamilyMember
{
// put some properties here
}
public interface IMyClass<T> where T: IWeirdFamilyMember
{
string Name { get; set; }
int Age { get; set; }
T Weird { get; set; }
}
最后,您可以将类定义为:
public class Version_1 : IMyClass<WeirdDad>
{
string Name { get; set; }
int Age { get; set; }
WeirdDad Weird { get; set; }
}
public class Version_2 : IMyClass<WeirdChild>
{
string Name { get; set; }
int Age { get; set; }
WeirdChild Weird { get; set; }
}
Ksv3n的例子的问题是,你假设开发人员正在将WeirdDad的WeirdChild子类分配给Weird属性,而实际上,它可以是任何类型的WeirdDa,也可以是WeirdDade本身。这是运行时异常的配方。
我同意@CodingMadeEasy的观点,即使用new是一种气味代码。
因此,我建议使用显式接口实现。
interface IVersion_1
{
WeirdDad Weird { get; set; }
}
interface IVersion_2
{
WeirdChild Weird { get; set; }
}
class Version_1 : IVersion_1
{
public string Name { get; set; }
public int Age { get; set; }
public WeirdDad Weird { get; set; }
public MysteriousDad Mysterious { get; set; }
}
class Version_2 : Version_1, IVersion_2
{
public string IdentityCode { get; set; }
WeirdChild IVersion_2.Weird { get; set; }
}
因此,在客户端中,根据接口的类型,正确的属性将被称为