2013-09-08 5 views
1

Я пытаюсь придумать очень аккуратный способ изменить существующий класс. Я попытаюсь объяснить, что я придумал, используя этот пример;Переопределение метода класса с помощью класса-обертки

abstract class AbstractX 
{ 
    public abstract string X(); 
    protected internal abstract int Y(); 
} 

// Execute all methods on another instance of AbstractX 
// This is why the method(s) are 'protected *internal*' 
class WrappedX : AbstractX 
{ 
    AbstractX _orig; 
    public WrappedX(AbstractX orig) 
    { 
     _orig = orig; 
    } 

    public override string X() 
    { 
     return _orig.X(); 
    } 
    protected internal override int Y() 
    { 
     return _orig.Y(); 
    } 
} 

// The AbstractX implementation I start with 
class DefaultX : AbstractX 
{ 
    public override string X() 
    { 
     // do stuff 

     // call Y, note that this would never call Y in WrappedX 
     var y = Y(); 

     return y.ToString(); 
    } 
    protected internal override int Y() 
    { 
     return 1; 
    } 
} 

// The AbstractX implementation that should be able to alter *any* other AbstractX class 
class AlteredX : WrappedX 
{ 
    public AlteredX(AbstractX orig) 
     :base(orig) 
    { 
    } 

    protected internal override int Y() 
    { 
     Console.WriteLine("Sweet, this can be added to any AbstractX instance!"); 

     return base.Y(); 
    } 
} 

Правильно, так что я намерен использовать это;

AbstractX x = new DefaultX(); 
x = new AlteredX(x); 
Console.WriteLine(x.X()); // Should output 2 lines 

Или отойти от абстрактного примера на второй и сделать его более конкретным (должно быть понятно);

FileWriterAbstract writer = new FileWriterDefault("path/to/file.ext"); 
writer = new FileWriterSplit(writer, "100MB"); 
writer = new FileWriterLogged(writer, "path/to/log.log"); 
writer.Write("Hello"); 

Но (вернемся к абстрактному примеру) это не сработает. Момент AlteredX.X() называется (не переопределяется) она переходит к WrappedX.X(), который, конечно, работает DefaultX.X(), который использует это собственныйY() метод, а не один я определил в AlteredX. Он даже не знает, она существует.

Я надеюсь, что это очевидно, почему я хочу, чтобы это сработало, но я объясню далее, чтобы убедиться;

Если я не использую WrappedX к созданному AlteredX, AlteredX не будет «applyable» в любой AbstractX экземпляр, таким образом, что делает что-то вроде FileWriter выше невозможно. Вместо;

FileWriterAbstract 
FileWriterDefault : FileWriterAbstract 
FileWriterWrap : FileWriterAbstract 
FileWriterSplit : FileWriterWrap 
FileWriterLogged : FileWriterWrap 

Это стало бы;

FileWriterAbstract 
FileWriterDefault : FileWriterAbstract 
FileWriterSplit : FileWriterDefault 
// Implement Logged twice because we may want to use it with or without Split 
FileWriterLogged : FileWriterDefault 
FileWriterLoggedSplit : FileWriterSplit 

И если я тогда создал новый, я должен был бы реализовать его в 4 раза, потому что я хочу его пригодным для использования с;

Default 
Split 
Logged 
Split+Logged 

И так далее ...

Так с этим в виду, что это лучший способ для достижения этой цели? Лучшее, что я мог придумать (непроверенный);

class DefaultX : AbstractX 
{ 
    protected internal override Func<string> xf { get; set; } 
    protected internal override Func<int> yf { get; set; } 

    public DefaultX() 
    { 
     xf = XDefault; 
     yf = YDefault; 
    } 

    public override string X() 
    { 
     return xf(); 
    } 

    protected override int Y() 
    { 
     return yf(); 
    } 

    string XDefault() 
    { 
     var y = Y(); 

     return y.ToString(); 
    } 

    int YDefault() 
    { 
     return 1; 
    } 
} 

class AlteredX : WrappedX 
{ 
    Func<int> _yfOrig { get; set; } 

    public AlteredX() 
    { 
     // I'm assuming this class member doesn't get overwritten when I set 
     // base.yf in the line below. 
     _yfOrig = base.yf; 

     base.yf = YAltered; 
    } 

    private int YAltered() 
    { 
     Console.WriteLine("Sweet, this can be added to any AbstractX instance!"); 

     return yfOrig(); 
    } 
} 

Даже если это действительно работает, кажется действительно беспорядочным ... есть ли у кого-нибудь предложения?

+0

Что вы придумали, как выглядит шаблон Decorator, http://en.wikipedia.org/wiki/ Decorator_pattern – tvanfosson

+0

@Alireza Я знаю, это не меняет того факта, что я ищу решение проблемы. – natli

+0

@tvanfosson Да, похоже. Эта страница wiki показывает только примеры, которые не пытаются переопределить определенные методы, вызываемые общедоступными методами, поэтому мой вопрос стоит. – natli

ответ

0

Один из способов решения этой проблемы - отложить все внутренние операции до отдельного, возможно, внутреннего класса служебных программ, и предоставить возможность классам-оболочкам заменить реализацию класса утилиты. Примечание. В этом примере для реализации класса утилиты требуется какой-либо конкретный класс без оболочки. Класс-оболочка может или не может включить оболочку класса. Ключевым моментом здесь является то, что getter/setter для класса utilities в базовом (абстрактном) классе не позволяет его переопределять, поэтому каждый наследующий класс использует класс утилиты, определенный его конструктором.Если он не хочет создавать свои собственные утилиты, он по умолчанию соответствует классу, который он обертывает, и в конечном итоге все возвращается к конкретному, не-обернутому корневому классу композиции, если это необходимо.

ПРИМЕЧАНИЕ: это очень сложно, и я бы избегал этого. Если возможно, используйте стандартный декоратор и полагайтесь только на методы открытого интерфейса обернутого класса. Кроме того, классы полезности не обязательно должны быть внутренними классами. Они могут быть введены через конструктор, который может сделать его немного чище. Затем вы явно используете шаблон Decorator для утилит.

public interface IFoo 
{ 
    string X(); 
} 

public abstract class AbstractFoo : IFoo 
{ 
    public abstract string X(); 

    protected internal Footilities Utilities { get; set; } 

    protected internal abstract class Footilities 
    { 
     public abstract int Y(); 
    } 
} 

public class DefaultFoo : AbstractFoo 
{ 
    public DefaultFoo() 
    { 
     Utilities = new DefaultFootilities(); 
    } 

    public override string X() 
    { 
     var y = Utilities.Y(); 

     return y.ToString(); 
    } 

    protected internal class DefaultFootilities : Footilities 
    { 
     public override int Y() 
     { 
      return 1; 
     } 
    } 
} 

public abstract class AbstractWrappedFoo : AbstractFoo 
{ 
    protected readonly AbstractFoo Foo; 

    public AbstractWrappedFoo(AbstractFoo foo) 
    { 
     Foo = foo; 
    } 

    public override string X() 
    { 
     return Foo.X(); 
    } 
} 

public class LoggedFoo : AbstractWrappedFoo 
{ 
    public LoggedFoo(AbstractFoo foo) 
     : base(foo) 
    { 
     Foo.Utilities = new LoggedUtilities(Foo.Utilities); 
    } 

    public override string X() 
    { 
     return Foo.X(); 
    } 


    protected internal class LoggedUtilities : Footilities 
    { 
     private readonly Footilities _utilities; 

     public LoggedUtilities(Footilities utilities) 
     { 
      _utilities = utilities; 
     } 

     public override int Y() 
     { 
      Console.WriteLine("Sweet"); 
      return _utilities.Y(); 
     } 
    } 
} 

Теперь эта программа

class Program 
{ 
    static void Main(string[] args) 
    { 
     AbstractFoo foo = new LoggedFoo(new DefaultFoo()); 

     Console.WriteLine(foo.X()); 
    } 
} 

Производит

Sweet! 
1 
+0

Спасибо за ваш ответ. Во многих отношениях это то же самое, что и в конце моего вопроса, за исключением того, что вы используете класс, а не Action/Func. Я предполагаю, что это означает, что нет лучшей альтернативы, поэтому я буду придерживаться ее. – natli

+0

@ natli Оба кажутся слишком сложными, но трудно дать вам лучший совет, учитывая абстрактную проблему. Я думаю, что я попытаюсь удалить повторное использование внутренних/защищенных методов. Трудно сказать, как не видеть, что вы делаете в реальной проблеме. – tvanfosson

0

Я думаю, что вы перепутали композицию с наследованием.

Когда вы вызываете x.X() на объект AlteredX, объект вызывает метод X его базового объекта (WrappedX). Сам базовый объект вызывает объект типа DefaultX, который он уже завернул. Теперь метод Y вызывается на объект DefaultX (_orig). Вы ожидаете, что кто-то знает, что в вызывающем абоненте есть что-то сверхъестественное! Но как?

В этой цепочке вызовов я не вижу точки, в которой участвует переопределение метода Y.

+0

Вы читали вопрос до конца? Я уже знаю, что это проблема, но я все еще ищу решение для того, чтобы делать то, что я пытаюсь сделать. – natli