0

Here's a silly example of a tree structure where every node does a different kind of an action, but exactly one node (any node) has to do some common work at the start and end of a project.

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

The problem is, you can't call the protected method DoActualWork() on a base class.

Two solutions I see are:

  • Make DoActualWork() public. But this would allow anyone to call it without AnnounceProjectToNewspapers() or PutProductOnmarket().
  • Make DoActualWork() internal. But this would prevent other assemblies from using the "management system".

Is there a standard work-around people use to avoid this limitation?

relatively_random
  • 4,505
  • 1
  • 26
  • 48
  • What do you mean by *“you can't call the protected method `DoActualWork()` on a base class”*. So far your design is common practice and I don't see an issue with it. – Ondrej Tucny Oct 13 '16 at 17:19
  • If I understand you correctly, you need to make the DoActualWork private for the Manager class and the same time available to external callers. This is a bit confusing as it contradicts the abstraction principal of inheritance. I think you need to separate the work implementation into a separate class let's call it Task and make its members public/private as needed. – Islam Yahiatene Oct 13 '16 at 18:41
  • @OndrejTucny Protected members can only be accessed from the derived class by calling them for an object of that derived class's static type or a type derived from it. So `Manager` can't call `subordinate.DoActualWork()` because subordinate is not a `Manager` or derived from it. – relatively_random Oct 13 '16 at 18:48
  • @IslamYahiatene I want to make `DoActualWork` "private" to the `Employee` class and all classes deriving from it, but still allow the derived classes to override it. – relatively_random Oct 13 '16 at 18:49
  • 1
    `Manager` class need access to implementation of `DoActualWork` method of subordinate instances of `Employee` class. This mean that `DoActualWork` need to be `public` or at least `internal`. With your current design trying to achive goal you want will be some "hacky" workaround. – Fabio Oct 13 '16 at 19:49
  • @relatively_random: if you need to expose that protected method, then simply write a public method in the derived class which in turn calls that protected method. Essentially, you are proxying out the protected method - a.k.a. facading. – code4life Oct 13 '16 at 21:58
  • @code4life Let's say I call this derived class Renegade: it would essentially allow public calls to DoActualWork. In this scenario, Managers can only manage Renegade subordinates, not any employee. – relatively_random Oct 14 '16 at 06:20

3 Answers3

1

I have a couple of ways to achieve what you need.

  1. The first and obvious way is to inherit Engineer and Salesman from Manager instead of employee. But this makes the inheritance sound ridiculous. Every Engineer is a Manager (I wish that was true.)

  2. As you've mentioned,

Make DoActualWork() public

This does work. To then prevent any other method from calling DoActualWork() you could find out the type of the class using reflection/stacktrace and block invalid types. Refer here

But both these methods feel clanky to me. There should definitely be a way to design the classes to get what you need.

Community
  • 1
  • 1
Vignesh
  • 504
  • 5
  • 13
  • Thanks for the link! I'd rather try avoiding reflection, but I'll look into it. – relatively_random Oct 13 '16 at 18:54
  • I went with option number 1 for the specific problem I had: removed the manager class and made every employee be able to carry subordinates. This slightly broken abstraction shouldn't be too confusing. – relatively_random Oct 16 '16 at 10:31
0

I came up with a solution like this (this has not been tested)

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;
}
Islam Yahiatene
  • 1,441
  • 14
  • 27
  • If I'm not wrong, this is equivalent to declare DoActualWork() as public (and a bit overcomplicated): every client will be able to call DoWork() (and therefore DoActualWork()) OP would like to avoid it – Gian Paolo Oct 13 '16 at 20:32
  • Actually, DoActualWork() is publicly accessible even without setting it public because it can be called by another public method (StartupProject). He needs to redesign the entire hierarchy. – Islam Yahiatene Oct 13 '16 at 21:00
  • @IslamYahiatene By this reasoning, every single private method in existence is publicly accessible because it gets executed as a consequence of a call to one of its class's public methods. `DoActualWork`, in my example, can only ever be called from an outside within its rightful context: after `AnnounceProjectToNewspapers` and before `PutProductOnMarket`. – relatively_random Oct 13 '16 at 21:25
0

Thinking about all the responses, a compromise just crossed my mind: use an indirect call through an internal method. In other words, a poor man's friend declaration by informal comments.

Here are the relevant bits:

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();
    }
}

Deriving classes from other assemblies will be able to customize the work Employees and Managers do, but they will not be able to make anyone do work without proper context.

Classes from within the assembly will be able to do anything, but let's assume you can trust the people developing the framework to follow the instructions in comments.

relatively_random
  • 4,505
  • 1
  • 26
  • 48
  • This type of (what I call) "contrived complexity: is exactly the reason why we need to adopt the Single Responsibility Principle along with inversion of control mechanics. Otherwise, you will undoubtedly end up writing these kinds of code which makes you wonder, "what is the reason for hiding the code in the first place?" – code4life Oct 13 '16 at 22:00
  • @code4life Thanks for the input. Could you, please, elaborate or post an example of how you would restructure the code? I do believe I have a decent understanding of the language, but I'm not that skilled with OOP. – relatively_random Oct 13 '16 at 22:11
  • I just noticed your code - have you tried compiling it? It shouldn't work, actually. Also, you've introduced a nifty paradox (which kind of reminds me of an ourobouros, lol). It might be better to just sit down and draw out the path of execution that you want to happen, and then figure this out. It's a bit hard to tell where you want the actual logic to be implemented - is the main code supposed to be at the parent level, or is the parent level code supposed to merely "direct the traffic"? I'll post an answer once you respond to this. – code4life Oct 13 '16 at 22:20
  • @code I don't have access to my computer at the moment so I used an online compiler and yes, it did compile. Conceptually, what I'd like is a tree structure where each node is tied to some arbitrary work. Some types of nodes cannot have children, others can. User has direct access to all the nodes and can make an arbitrary subtree do their part of the work, but every such batch execution must have the same preparation and cleanup work done. Sorry if it's a bit of a convoluted explanation. – relatively_random Oct 13 '16 at 22:43
  • argh - yeah, I misread your post about putting only the relevant bits of code in. The wiring is still a bit paradoxical, though. But now I understand what you want to do, and your approach is pretty close to the general practice. I'll try to post something up when I have some free time today or tomorrow. – code4life Oct 14 '16 at 12:01