【问题标题】:Avoiding the call to protected member of base class避免调用基类的受保护成员
【发布时间】:2017-02-22 22:08:37
【问题描述】:

这是一个树结构的愚蠢示例,其中每个节点都执行不同类型的操作,但恰好一个节点(任何节点)必须在项目开始和结束时执行一些共同的工作。

public abstract class Employee
{
    public void StartProject()
    {
        AnnounceProjectToNewspapers();
        DoActualWork();
        PutProductOnMarket();
    }

    protected abstract void DoActualWork();

    private void AnnounceProjectToNewspapers() { }
    private void PutProductOnMarket() { }
}

public class Engineer : Employee
{
    protected override void DoActualWork()
    {
        // Build things.
    }
}

public class Salesman : Employee
{
    protected override void DoActualWork()
    {
        // Design leaflets.
    }
}

public class Manager : Employee
{
    protected override void DoActualWork()
    {
        // Make gantt charts.

        // Also delegate.
        foreach (var subordinate in subordinates)
            // ...but then compiler stops you.
            subordinate.DoActualWork();
    }

    private List<Employee> subordinates;
}

问题是,您不能在基类上调用受保护的方法DoActualWork()

我看到的两个解决方案是:

  • DoActualWork() 公开。 但这将允许任何人在没有AnnounceProjectToNewspapers()PutProductOnmarket() 的情况下调用它。
  • DoActualWork()设为内部。但这会阻止其他程序集使用“管理系统”。

是否有人们用来避免这种限制的标准变通方法?

【问题讨论】:

  • 你说的“你不能在基类上调用受保护的方法DoActualWork()”是什么意思。到目前为止,您的设计是常见的做法,我认为它没有问题。
  • 如果我理解正确,您需要将 DoActualWork 设为 Manager 类私有,同时外部调用者也可以使用。这有点令人困惑,因为它与继承的抽象原则相矛盾。我认为您需要将工作实现分离到一个单独的类中,我们将其称为 Task 并根据需要使其成员公开/私有。
  • @OndrejTucny 受保护的成员只能通过为派生类的静态类型或派生类型的对象调用派生类来访问。所以Manager 不能调用subordinate.DoActualWork(),因为从属不是Manager 或派生自它。
  • @IslamYahiatene 我想让DoActualWork“私有”到Employee 类和所有从它派生的类,但仍然允许派生类覆盖它。
  • Manager 类需要访问Employee 类的从属实例的DoActualWork 方法的实现。这意味着DoActualWork 需要为public 或至少为internal。随着您当前的设计试图实现您想要的目标,这将是一些“hacky”的解决方法。

标签: c# oop polymorphism


【解决方案1】:

考虑到所有的响应,我想到了一个折衷方案:通过内部方法使用间接调用。换句话说,非正式的cmets的一个穷人的朋友声明。

以下是相关位:

public abstract class Employee
{
    // Meant to be called by Manager class only.
    internal void FollowTheLeader()
    {
        DoActualWork();
    }
}

public class Manager : Employee
{
    protected override void DoActualWork()
    {
        // Make gantt charts.

        // Also delegate.
        foreach (var subordinate in subordinates)
            // ...but then compiler stops you.
            subordinate.FollowTheLeader();
    }
}

从其他程序集派生类将能够自定义员工和经理所做的工作,但他们无法让任何人在没有适当上下文的情况下工作。

程序集中的类可以做任何事情,但我们假设您可以相信开发框架的人会按照 cmets 中的说明进行操作。

【讨论】:

  • 这种(我称之为)“人为的复杂性:正是我们需要采用单一责任原则以及控制机制反转的原因。否则,您无疑最终会编写这些类型让你想知道的代码,“首先隐藏代码的原因是什么?”
  • @code4life 感谢您的意见。请您详细说明或发布如何重组代码的示例?我确实相信我对这门语言有很好的理解,但我对 OOP 的熟练程度并不高。
  • 我刚刚注意到你的代码——你试过编译它吗?实际上,它不应该工作。此外,您还引入了一个绝妙的悖论(这让我想起了衔尾蛇,哈哈)。最好只是坐下来画出你想要发生的执行路径,然后弄清楚这一点。很难说出您希望在哪里实现实际逻辑 - 主代码应该在父级别,还是父级别代码应该只是“引导流量”?一旦您对此做出回应,我将发布答案。
  • @code 我目前无法访问我的计算机,所以我使用了在线编译器,是的,它确实编译了。从概念上讲,我想要的是一个树结构,其中每个节点都与一些任意工作相关联。有些类型的节点不能有子节点,有些可以。用户可以直接访问所有节点,并且可以让任意子树完成他们的工作,但是每次这样的批处理执行都必须完成相同的准备和清理工作。抱歉,解释有点复杂。
  • argh - 是的,我误读了您关于只放入相关代码位的帖子。不过,接线仍然有点自相矛盾。但现在我明白了你想要做什么,而且你的方法非常接近一般做法。今天或明天有空的时候我会尝试发布一些东西。
【解决方案2】:

我想出了一个这样的解决方案(这还没有经过测试)

public interface IEmployee {
    void StartProject();
    void DoWork();
}

public abstract class EmployeeBase : IEmployee {
    public void StartProject() {
        AnnounceProjectToNewspapers();
        DoActualWork();
        PutProductOnMarket();
    }

    void IEmployee.DoWork() {
        // Maybe set a flag to see whether the work has been already done by calling StartProject().
        this.DoActualWork();
    }

    protected abstract void DoActualWork();

    private void AnnounceProjectToNewspapers() { }
    private void PutProductOnMarket() { }
}

public class Employee : EmployeeBase {
    protected override void DoActualWork() {
        // Log system Action
    }
}

public class Engineer : Employee {
    protected override void DoActualWork() {
        // Build things.
    }
}

public class Salesman : Employee {
    protected override void DoActualWork() {
        // Design leaflets.
    }
}

public class Manager : Employee {
    protected override void DoActualWork() {
        DoWorkInternal();
    }

    private void DoWorkInternal() {
        foreach (var subordinate in subordinates)
            subordinate.DoWork();
    }

    private List<IEmployee> subordinates;
}

【讨论】:

  • 如果我没记错的话,这相当于将 DoActualWork() 声明为 public(并且有点过于复杂):每个客户端都可以调用 DoWork() (因此 DoActualWork())OP想避免它
  • 实际上,DoActualWork() 是可以公开访问的,即使没有将其设置为 public,因为它可以被另一个公共方法 (StartupProject) 调用。他需要重新设计整个层次结构。
  • @IslamYahiatene 通过这种推理,存在的每一个私有方法都可以公开访问,因为它是作为调用其类的公共方法之一的结果而执行的。在我的示例中,DoActualWork 只能在其合法上下文中从外部调用:在AnnounceProjectToNewspapers 之后和PutProductOnMarket 之前。
【解决方案3】:

我有几种方法可以满足您的需求。

  1. 第一个也是显而易见的方法是从经理而不是员工继承工程师和销售员。但这让继承听起来很荒谬。每个工程师都是经理(我希望这是真的。)

  2. 正如你所提到的,

公开 DoActualWork()

这确实有效。然后,为了防止任何其他方法调用 DoActualWork(),您可以使用反射/堆栈跟踪找出类的类型并阻止无效类型。参考here

但这两种方法对我来说都感觉很吵。绝对应该有一种方法来设计类以满足您的需求。

【讨论】:

  • 感谢您的链接!我宁愿尽量避免反思,但我会调查一下。
  • 针对我遇到的具体问题,我选择了选项 1:删除了经理类,让每个员工都能够携带下属。这种略微破碎的抽象不应该太令人困惑。
猜你喜欢
  • 2016-07-23
  • 2016-10-01
  • 2019-12-05
  • 1970-01-01
  • 1970-01-01
  • 2016-07-15
  • 1970-01-01
  • 2019-01-20
相关资源
最近更新 更多