RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 575837
Accepted
Pandasama
Pandasama
Asked:2020-10-10 13:47:53 +0000 UTC2020-10-10 13:47:53 +0000 UTC 2020-10-10 13:47:53 +0000 UTC

如何设计类层次结构以方便向基类添加属性?

  • 772

同志们,告诉我在一个体面的社会中通常如何解决这样的问题:

例如,我们有这样一个类层次结构

class A_Base
{
  int x;
  int y;

  public A_Base(int x, int y) 
  { 
    this.x = x;
    this.y = y;
  }
}

class A_First : A_Base
{
  string name;

  public A_First(int x, int y, string name):base(x,y) 
  { 
    this.name = name; 
  }
}

class A_Second : A_First
{
  string surname;

  public A_Second(int x, int y, string name, string surname):base(x,y,name)
  {
    this.surname = surname;
  }
}

现在我们需要向 A_Base 添加一个属性(并相应地在构造函数中对其进行初始化)

class A_Base
{
  int x;
  int y;
  int z;

  public A_Base(int x, int y, int z) 
  { 
    this.x = x;
    this.y = y;
    this.z = z;
  }
}

这意味着我们将不得不更改整个类层次结构中构造函数的定义,在那里添加一个新参数 z,尽管我们不更改这些构造函数本身——我们只更改对父类的调用。而且不知何故它不是很好。

class A_First : A_Base
{
  string name;

  public A_First(int x, int y, int z, string name):base(x,y,z) 
  { 
    this.name = name; 
  }
}

高级OOP的同志遇到这种情况一般是怎么操作的呢?有一个单独的类/结构将作为构造函数的参数吗?

class A_Init
{
  int x;
  int y;
  int z;
}

class A_Base
{
  int x;
  int y;
  int z;

  public A_Base(A_Init init) 
  { 
    this.x = init.x;
    this.y = init.y;
    this.z = init.z;
  }
}

还是有其他一些棘手的模式?

c#
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Мистер Фикс
    2020-10-10T18:26:14Z2020-10-10T18:26:14Z

    这是继承的缺点之一——如果父类发生变化,那么这些变化很可能会影响所有子类。在这里什么也做不了。这种缺点在长期的、大规模的项目中会表现得尤为明显。

    为了避免这样的事情,可以考虑用组合代替继承,或者熟悉 SOLID 设计,或者看看其他范例,例如“面向组件的编程”。

    在后者中,每个类都是由组件构建的,没有分别使用继承,问题消失了,出错的概率也小了很多,但是当组件很多时,导航起来会很困难。

    • 10
  2. VladD
    2020-10-10T18:54:10Z2020-10-10T18:54:10Z

    在 C# 语言中,没有办法。这是脆性基类问题的特例。

    您必须明白,更改基类(某处有或可能有派生类)始终是重大更改,需要仔细审查整个代码。

    这就是为什么

    • 尽量提前规划对继承开放的公共基类,以便将来不需要更改其公共接口;
    • 尽量抵制这种变化;
    • 如果您仍然无法避免对基类进行更改,请将您的更改通知您的基类的所有可能客户,以便他们审查他们的代码;尝试以编译器指向需要更改的位置的方式进行更改。
      • 不好的例子:你的函数以度数为单位的角度作为类型的数字double,现在它以弧度为单位的角度以及类型的数量double
      • 更好的例子:你的函数以角度为单位,现在你有一个新名称的函数,以弧度为单位,旧的用属性标记[Obsolete]

    是的,修改基类违反了SOLID 的开放/封闭原则。

    • 7
  3. Best Answer
    Mark Shevchenko
    2020-10-10T19:37:44Z2020-10-10T19:37:44Z

    tl;博士

    解决方案可能有多种,具体要视具体情况而定。

    在这种特殊情况下,我会注意更改规范的原因。新参数从哪里来z,为什么没有立即提供?

    这个问题的答案将有助于正确设计层次结构。例如(选项 #1),主题专家无法描述所有可能的场景。这意味着当您处理程序时,属性会出现和改变,从业务角度来看这是完全正常的。那么是的,类型结构A_Init将是一个合适的解决方案。

    另一方面,如果此结构存储参数x, y,的默认值z,那么立即在层次结构中创建一个空构造函数或仅包含最必要参数的构造函数不是更好吗,其列表肯定会不改变©?

    第二种方法更正确,除其他外,因为它有助于在层次结构的所有级别解决相同的问题。A_Init不保证您不必A_Init2为任何继承人开始。

    因为:尽可能使用默认值。仅通过构造函数传递readonly字段初始值设定项。

    选项编号 2:正在进行系统的初步设计,正在编写原型。对于原型来说,经常更改代码并没有那么可怕。实际上,它的开发是为了识别瓶颈。那么答案就是:没有什么可怕的事情发生,应该如此。

    第三种选择:继承人不是真正的继承人,而是装饰者。然后实现看起来不同:

    class A_First : A_Base
    {
      A_Base decorating;
      string name;
    
      public A_First(A_Base decirating, string name)
      { 
        this.decorating = decorating;
        this.name = name; 
      }
    }
    

    如何理解在这种情况下利害攸关的是什么?通常这样它A_Base是一个几乎什么都不能做的抽象类,但是它的继承者做主要的工作。

    第四个选项是第一个选项的扩展。如果存在默认值,但获取它们的算法令人困惑,则获取/计算它们的逻辑可以在一个单独的类中取出,Eric Evans 将其称为regulation。然后层次结构中的所有类都会在构造函数中接受一个或多个规则,并根据业务逻辑规则进行初始化。

    与规定的区别A_Init不在于句法,而在于本质。如果A_Init它不包含业务逻辑,而只是返回常量,那么这很可能不是规定。

    • 7

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5