替换继承中的对象

本文关键字:对象 继承 替换 | 更新日期: 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_1Version_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的评论讨论的激励

最少知识原则说,客户不应该知道内部细节才能使用类。有人可能会争辩说,需要知道如何组成正确的VersionWeird是一种违规行为。

假设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; }
} 

因此,在客户端中,根据接口的类型,正确的属性将被称为