简体   繁体   中英

C# has abstract classes and interfaces, should it also have “mixins”?

Every so often, I run into a case where I want a collection of classes all to possess similar logic. For example, maybe I want both a Bird and an Airplane to be able to Fly() . If you're thinking "strategy pattern", I would agree, but even with strategy, it's sometimes impossible to avoid duplicating code.

For example, let's say the following apply (and this is very similar to a real situation I recently encountered):

  1. Both Bird and Airplane need to hold an instance of an object that implements IFlyBehavior .
  2. Both Bird and Airplane need to ask the IFlyBehavior instance to Fly() when OnReadyToFly() is called.
  3. Both Bird and Airplane need to ask the IFlyBehavior instance to Land() when OnReadyToLand() is called.
  4. OnReadyToFly() and OnReadyToLand() are private.
  5. Bird inherits Animal and Airplane inherits PeopleMover .

Now, let's say we later add Moth , HotAirBalloon , and 16 other objects, and let's say they all follow the same pattern.

We're now going to need 20 copies of the following code:

private IFlyBehavior _flyBehavior;

private void OnReadyToFly()
{
    _flyBehavior.Fly();
}

private void OnReadyToLand()
{
    _flyBehavior.Land();
}

Two things I don't like about this:

  1. It's not very DRY (the same nine lines of code are repeated over and over again). If we discovered a bug or added a BankRight() to IFlyBehavior , we would need to propogate the changes to all 20 classes.

  2. There's not any way to enforce that all 20 classes implement this repetitive internal logic consistently. We can't use an interface because interfaces only permit public members. We can't use an abstract base class because the objects already inherit base classes, and C# doesn't allow multiple inheritance (and even if the classes didn't already inherit classes, we might later wish to add a new behavior that implements, say, ICrashable , so an abstract base class is not always going to be a viable solution).

What if...?

What if C# had a new construct, say pattern or template or [fill in your idea here], that worked like an interface, but allowed you to put private or protected access modifiers on the members? You would still need to provide an implementation for each class, but if your class implemented the PFlyable pattern, you would at least have a way to enforce that every class had the necessary boilerplate code to call Fly() and Land() . And, with a modern IDE like Visual Studio, you'd be able to automatically generate the code using the "Implement Pattern" command.

Personally, I think it would make more sense to just expand the meaning of interface to cover any contract, whether internal (private/protected) or external (public), but I suggested adding a whole new construct first because people seem to be very adamant about the meaning of the word "interface", and I didn't want semantics to become the focus of people's answers.

Questions:

Regardless of what you call it, I'd like to know whether the feature I'm suggesting here makes sense. Do we need some way to handle cases where we can't abstract away as much code as we'd like, due to the need for restrictive access modifiers or for reasons outside of the programmer's control?

Update

From AakashM's comment, I believe there is already a name for the feature I'm requesting: a Mixin . So, I guess my question can be shortened to: "Should C# allow Mixins?"

The problem you describe could be solved using the Visitor pattern (everything can be solved using the Visitor pattern, so beware! )

The visitor pattern lets you move the implementation logic towards a new class. That way you do not need a base class, and a visitor works extremely well over different inheritance trees.

To sum up:

  1. New functionality does not need to be added to all different types
  2. The call to the visitor can be pulled up to the root of each class hierarchy

For a reference, see the Visitor pattern

Visual Studio already offers this in 'poor mans form' with code snippets. Also, with the refactoring tools a la ReSharper (and maybe even the native refactoring support in Visual Studio), you get a long way in ensuring consistency.

[EDIT: I didn't think of Extension methods, this approach brings you even further (you only need to keep the _flyBehaviour as a private variable). This makes the rest of my answer probably obsolete...]

However; just for the sake of the discussion: how could this be improved? Here's my suggestion.

One could imagine something like the following to be supported by a future version of the C# compiler:

// keyword 'pattern' marks the code as eligible for inclusion in other classes
pattern WithFlyBehaviour
{
   private IFlyBehavior_flyBehavior;

   private void OnReadyToFly()
   {
      _flyBehavior.Fly();
   }

   [patternmethod]
   private void OnReadyToLand()
   {
      _flyBehavior.Land();
   }     
}

Which you could use then something like:

 // probably the attribute syntax can not be reused here, but you get the point
 [UsePattern(FlyBehaviour)] 
 class FlyingAnimal
 {
   public void SetReadyToFly(bool ready)
   {
      _readyToFly = ready;
      if (ready) OnReadyToFly(); // OnReadyToFly() callable, although not explicitly present in FlyingAnimal
   }

 }

Would this be an improvement? Probably. Is it really worth it? Maybe...

Cant we use extension methods for this

    public static void OnReadyToFly(this IFlyBehavior flyBehavior)
    {
          _flyBehavior.Fly()
    }

This mimics the functionality you wanted (or Mixins)

You just described aspect oriented programming .

One popular AOP implementation for C# seems to be PostSharp (Main site seems to be down/not working for me though, this is the direct "About" page).


To follow up on the comment: I'm not sure if PostSharp supports it, but I think you are talking about this part of AOP:

Inter-type declarations provide a way to express crosscutting concerns affecting the structure of modules. Also known as open classes, this enables programmers to declare in one place members or parents of another class, typically in order to combine all the code related to a concern in one aspect.

I will use extension methods to implement the behaviour as the code shows.

  1. Let Bird and Plane objects implement a property for IFlyBehavior object for an interface IFlyer

     public interface IFlyer { public IFlyBehavior FlyBehavior } public Bird : IFlyer { public IFlyBehaviour FlyBehavior {get;set;} } public Airplane : IFlyer { public IFlyBehaviour FlyBehavior {get;set;} } 
  2. Create an extension class for IFlyer

     public IFlyerExtensions { public void OnReadyToFly(this IFlyer flyer) { flyer.FlyBehavior.Fly(); } public void OnReadyToLand(this IFlyer flyer) { flyer.FlyBehavior.Land(); } } 

你能通过在.NET 4.0中使用新的ExpandoObject来获得这种行为吗?

Scala traits were developed to address this kind of scenario. There's also some research to include traits in C# .

UPDATE: I created my own experiment to have roles in C# . Take a look.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM