Присоединение переопределений к классу C # - PullRequest
2 голосов
/ 01 мая 2011

У меня есть класс с функциями:

class MyClass
{
    public List<Attachment> Attachments;

    public void A()
    {
        // Do something
    }
    public void B()
    {
        // Do something
    }
}

class AttachmentA : Attachment
{
    public void A()
    {
        // Do something else
        RealA();
    }
}

class AttachmentB : Attachment
{
    public void B()
    {
        // Do something else
        // RealB();
        // No need to call base function here
    }
}

Мне нужно в моем коде, когда я присоединяю AttachmentA к MyClass, что все функции в MyClass, которые также присутствуют в AttachmentAбыть переопределенным функциями в AttachmentA, а также предоставить доступ к исходным функциям в MyClass.
Например, я создаю MyClass, а затем присоединяю к нему экземпляр AttachmentA.вызов MyClass.A() фактически вызовет AttachmentA.A(), а AttachmentA.RealA() вызовет базовую функцию, которая была переопределена.
Я знаю, что это можно как-то сделать с помощью чего-то вроде использования списков обработчиков событий для обработки переопределений, но есть простой способреализовать это?
Редактировать: У меня нет проблем с длинным кодом, который использует отражение до тех пор, пока он присутствует, и не должен даже упоминаться ни в одной из функций - возможно, только при присоединении вложения.

Редактировать: вы хотели пример:

class MyClass
{
    public List<Attachment> Attachments;

    public MyClass()
    {
        Attachments = new List<Attachment>();
    }
    public void Attach(Attachment attachment)
    {
        Attachments.Add(attachment);
        // Do some magic here
    }
    public void A()
    {
        Console.WriteLine("MyClass.A");
    }
    public void B()
    {
        Console.WriteLine("MyClass.B");
    }
}

class AttachmentA : Attachment
{
    public void A()
    {
        Console.WriteLine("AttachmentA.A");
        RealA();
    }
}

class AttachmentB : Attachment
{
    public void B()
    {
        Console.WriteLine("AttachmentB.B");
    }
}

public class Program
{
    public static void Main(string[] Args)
    {
        MyClass aaa = new MyClass();
        aaa.A(); // should print MyClass.A
        aaa.B(); // should print MyClass.B
        aaa.Attach(new AttachmentA());
        aaa.Attach(new AttachmentB());
        aaa.A(); // should print AttachmentA.A <newline> MyClass.A
        aaa.B(); // should print AttachmentB.B
    }
}

Редактировать: я хочу достичь здесь, как юнит с атрибутами (= вложения).Когда устройство получает вложение RandomSpeed, RandomSpeed отменяет значение GetSpeed и возвращает случайное значение.когда он получит вложение уклонения, он переопределит функцию единиц ReduceHP, а иногда, основываясь на случайном значении, не вызовет базовую функцию.

Редактировать: Что действительно решит этот беспорядок, так это каким-то образом использует отражениечтобы изменить таблицы виртуальных методов, я собираюсь сделать продолжение по отдельному вопросу.Я держу этот вопрос здесь, если кто-то найдет лучший способ сделать это.

Ответы [ 5 ]

4 голосов
/ 01 мая 2011

Как уже упоминалось в комментариях, вы ищете шаблон Decorator.

http://en.wikipedia.org/wiki/Decorator_pattern

Шаблон декоратора можно использовать для сделать возможным расширить (украсить) функциональность определенного объекта во время выполнения, независимо от других экземпляры одного и того же класса, при условии некоторая основа сделана в дизайне время. Это достигается путем разработки новый класс декоратора, который оборачивает оригинальный класс.

2 голосов
/ 01 мая 2011

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

2 голосов
/ 01 мая 2011

Почему бы не воспользоваться другим подходом? Пусть вложения реализуют интерфейсы на основе того, что они хотят переопределить, например, ISpeedAttachment. Затем вы можете в функции базовой скорости перебирать вложения, которые реализуют ISpeedAttachment, вызывая их.

Пусть интерфейсы возвращают значение NULL, если они не вступили в силу, и затем вы можете проверить, что они все вернули значение NULL, и вызвать соответствующий базовый класс или передать параметр ref, который вы можете настроить по необходимости.

2 голосов
/ 01 мая 2011

Если вы не хотите вводить зависимости, такие как наследование или интерфейсы для реализации на MyClass, то:

Вы можете достичь этого с помощью делегатов.

Короче говоря, вы не можете переопределить функциюво время выполнения, не прибегая к какой-то непонятной магии отражения, но вы можете объявлять делегатов вместо функций.Когда вы создаете свой класс в конструкторе, заполняйте делегатов закрытыми методами, которые будут использоваться до тех пор, пока не появится класс AttachmentA. И используйте эти делегаты вместо методов.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    static class Program
    {

        static void Main(string[] args)
        {
            MyClass aaa = new MyClass();
            aaa.A(); // should print MyClass.A
            aaa.B(); // should print MyClass.B
            aaa.Attach(new AttachmentA());
            aaa.Attach(new AttachmentB());
            aaa.A(); // should print AttachmentA.A <newline> MyClass.A
            aaa.B(); // should print AttachmentB.B
        }

    }

    class MyClass
    {
        public List<Attachment> Attachments;

        public MyClass()
        {
            A = _A;
            B = _B;
            Attachments = new List<Attachment>();
        }
        public void Attach(Attachment attachment)
        {
            Attachments.Add(attachment);

            // this is your magic
            if (attachment.GetType() == typeof(AttachmentA)) {
                A = ((AttachmentA)attachment).A;
            }
            else if (attachment.GetType() == typeof(AttachmentB))
            {
                B = ((AttachmentB)attachment).B;
            }
        }

        public delegate void delegateA();
        public delegate void delegateB();

        public delegateA A;
        public delegateB B;

        public void _A()
        {
            Console.WriteLine("MyClass.A");
        }
        public void _B()
        {
            Console.WriteLine("MyClass.B");
        }
    }

    class Attachment { 
    }

    class AttachmentA : Attachment
    {
        public void A()
        {
            Console.WriteLine("AttachmentA.A");
        }
    }

    class AttachmentB : Attachment
    {
        public void B()
        {
            Console.WriteLine("AttachmentB.B");
        }
    }
}

Если вам нуженвыполнение всегда начинать в MyClass, а не в классе Attachment. Вы можете обернуть делегатов следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    static class Program
    {

        static void Main(string[] args)
        {
            MyClass aaa = new MyClass();
            aaa.A(); // should print MyClass.A
            aaa.B(); // should print MyClass.B
            aaa.Attach(new AttachmentA());
            aaa.Attach(new AttachmentB());
            aaa.A(); // should print AttachmentA.A <newline> MyClass.A
            aaa.B(); // should print AttachmentB.B
        }

    }

    class MyClass
    {
        public List<Attachment> Attachments;

        public MyClass()
        {
            Attachments = new List<Attachment>();
        }

        public void Attach(Attachment attachment)
        {
            Attachments.Add(attachment);

            if (attachment.GetType() == typeof(AttachmentA)) {
                _A = ((AttachmentA)attachment).A;
            }
            else if (attachment.GetType() == typeof(AttachmentB))
            {
                _B = ((AttachmentB)attachment).B;
            }
        }

        public delegate void delegateA();
        public delegate void delegateB();

        public delegateA _A;
        public delegateB _B;

        public void A()
        {
            if (_A != null)
            {
                _A();
            }
            else
            {
                Console.WriteLine("MyClass.A");
            }
        }
        public void B()
        {
            if (_B != null)
            {
                _B();
            }
            else
            {
                Console.WriteLine("MyClass.B");
            }
        }
    }

    class Attachment { 
    }

    class AttachmentA : Attachment
    {
        public void A()
        {
            Console.WriteLine("AttachmentA.A");
        }
    }

    class AttachmentB : Attachment
    {
        public void B()
        {
            Console.WriteLine("AttachmentB.B");
        }
    }
}

Вы можете сократить это до одного типа делегата, если A и B имеют одинаковые параметры и тип возврата в вашем реальномсценарий.

1 голос
/ 01 мая 2011

Я не уверен, возможно ли динамическое переопределение функциональности класса, но вы можете достичь чего-то похожего, используя разные интерфейсы. В зависимости от контекста, в котором вы хотите использовать это, может потребоваться лишь небольшая редизайн.

Стандартный способ сделать это будет следующим:

using System;

class MyClass
{
    public virtual void A()
    {
        Console.WriteLine("MyClass.A");
    }
    public virtual void B()
    {
        Console.WriteLine("MyClass.B");
    }
}

class ClassA : MyClass
{
    public override void A()
    {
        Console.WriteLine("AttachmentA.A");

        base.A();
    }
}

class ClassB : MyClass
{
    public override void B()
    {
        Console.WriteLine("AttachmentB.B");
    }
}

public class Program
{
    public static void Main(string[] Args)
    {
        MyClass aaa = new ClassA();
        MyClass bbb = new ClassB();

        aaa.A(); // prints MyClass.A
        aaa.B(); // prints MyClass.B
        (aaa as ClassA).A(); // prints AttachmentA.A
        (aaa as ClassA).B(); // prints MyClass.B
        bbb.A(); // prints MyClass.A
        bbb.B(); // prints MyClass.B
        (bbb as ClassB).A(); // prints AttachmentB.A + MyClass.A
        (bbb as ClassB).B(); // prints AttachmentB.B
    }
}

Вот еще один пример, похожий на предложенный Blowdart:

interface ICallMe
{
    bool A();
    bool B();
}

class MyClass
{
    public ICallMe Attachment { get; set; }

    public void A()
    {
        bool BaseFunction = true;
        if (Attachment != null)
            BaseFunction = Attachment.A();

        if (BaseFunction)
            Console.WriteLine("MyClass.A");
    }

    public void B()
    {
        bool BaseFunction = true;
        if (Attachment != null)
            BaseFunction = Attachment.B();

        if (BaseFunction)
            Console.WriteLine("MyClass.B");
    }
}

class ClassA : ICallMe
{
    public bool A()
    {
        Console.WriteLine("AttachmentA.A");

        return true;
    }

    public bool B()
    {
        Console.WriteLine("AttachmentA.B");

        return false;
    }
}

static class Program
{

    static void Main(string[] args)
    {
        MyClass aaa = new MyClass();
        aaa.A(); // prints MyClass.A
        aaa.B(); // prints MyClass.B
        aaa.Attachment = new ClassA();
        aaa.A(); // should print AttachmentA.A <newline> MyClass.A
        aaa.B(); // should print AttachmentB.B
    }
}

Это позволяет добавить только одно вложение. Если вы хотите переопределить поведение нескольких функций по отдельности, вы можете использовать какую-либо коллекцию для хранения вложений. Внутри базового класса вам нужно пройти через них и найти тот, который вы хотите выполнить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...