Перегрузка с иерархией классов - большинство производных не используется - PullRequest
6 голосов
/ 03 мая 2011

Проблема

Я пытаюсь избежать кода, который выглядит следующим образом:

If(object Is Man)
  Return Image("Man")
ElseIf(object Is Woman)
  Return Image("Woman")
Else
  Return Image("Unknown Object")

Я думал, что мог бы сделать это через перегрузку метода, но этовсегда выбирает наименее производный тип, я предполагаю, что это связано с тем, что перегрузка определяется во время компиляции (в отличие от переопределения), и поэтому в следующем коде можно предположить только базовый класс:

Структура кода:

NS:Real
   RealWorld (Contains a collection of all the RealObjects)
   RealObject
     Person
       Man
       Woman
NS:Virtual
   VirtualWorld (Holds a reference to the RealWorld, and is responsible for rendering)
   Image (The actual representation of the RealWorldObject, could also be a mesh..)
   ArtManager (Decides how an object is to be represented)

Реализация кода (ключевые классы):

class VirtualWorld
{
    private RealWorld _world;

    public VirtualWorld(RealWorld world)
    {
        _world = world;
    }

    public void Render()
    {
        foreach (RealObject o in _world.Objects)
        {
            Image img = ArtManager.GetImageForObject(o);
            img.Render();
        }
    }
}

static class ArtManager
{
    public static Image GetImageForObject(RealObject obj)// This is always used
    {
        Image img = new Image("Unknown object");
        return img;
    }

    public static Image GetImageForObject(Man man)
    {
        if(man.Age < 18)
            return new Image("Image of Boy");
        else
            return new Image("Image of Man");
    }

    public static Image GetImageForObject(Woman woman)
    {
        if (woman.Age < 70)
            return new Image("Image of Woman");
        else
            return new Image("Image of Granny");
    }
}

Мой сценарий: По сути, я создаю игру,и хотите отделить классы реального мира (например, человека) от экранных классов (изображение человека).Объект реального мира не должен знать о своем представлении на экране, представление должно быть осведомлено о реальном объекте (чтобы знать, сколько лет человеку и, следовательно, сколько морщин нарисовано).Я хочу иметь запасной вариант, когда, если RealObject неизвестного типа, он все равно отображает что-то (например, большой красный крест).

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

Какой самый элегантный способ решить эту проблему?- Без самого RealObject, содержащего информацию о том, как он должен быть представлен.Игра XNA - это подтверждение концепции, которая очень сложна для ИИ, и, если она окажется выполнимой, будет изменена с 2D на 3D (возможно, с поддержкой обоих типов для компьютеров более низкого уровня).

Ответы [ 5 ]

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

Использовать фабрику:

public class ImageFactory
{
    Dictionary<Type, Func<IPerson, Image>> _creators;

    void Assign<TPerson>(Func<IPerson, Image> imageCreator) where T : IPerson
    {
       _creators.Add(typeof(TPerson), imageCreator);
    }

   void Create(Person person)
   {
       Func<IPerson, Image> creator;
       if (!_creators.TryGetValue(person.GetType(), out creator))
          return null;

       return creator(person);
   }
}

Назначить фабричные методы:

imageFactory.Assign<Man>(person => new Image("Man");
imageFactory.Assign<Woman>(person => new Image("Big bad mommy");
imageFactory.Assign<Mice>(person => new Image("Tiny little mouse");

И использовать ее:

var imageOfSomeone = imageFactory.Create(man);
var imageOfSomeone2 = imageFactory.Create(woman);
var imageOfSomeone3 = imageFactory.Create(mice);

Чтобы иметь возможность возвращать разные изображения дляДля мужчин вы можете использовать условие:

factory.Assign<Man>(person => person.Age > 10 ? new Image("Man") : new Image("Boy"));

Для ясности вы можете добавить в класс все более сложные методы:

public static class PersonImageBuilders
{
    public static Image CreateMen(IPerson person)
    {
        if (person.Age > 60)
            return new Image("Old and gready!");
        else
            return new Image("Young and foolish!");

    }
}

И назначить метод

imageFactory.Assign<Man>(PersonImageBuilders.CreateMen);
1 голос
/ 03 мая 2011

Если вы используете .NET 4, попробуйте следующее:

Image img = ArtManager.GetImageForObject((dynamic)o);

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

0 голосов
/ 03 мая 2011

Я считаю, что причина вызова наименее производного класса в том, что вы выполняете работу во внешнем классе.Если вы сделаете метод GetImage () виртуальным членом класса RealObject, то должна вызываться самая производная версия.Обратите внимание, что вы можете иметь GetImage () делегировать ArtManager, если хотите.Но решение @ seairth выполняет то же самое и, вероятно, будет менее навязчивым.

Можно утверждать, что помещение GetImage () в класс RealObject нарушает Single Responsibility ... Я думаю, это будет зависеть от того, что остальныекласс выглядит так.Но мне кажется, что RealWorld.Render не должен отвечать за получение изображений для каждого RealObject.И как таковой, вам придется прикасаться к ArtManager каждый раз, когда вы добавляете подкласс RealObject, который нарушает Open / Closed.

0 голосов
/ 03 мая 2011

Если hiearchy RealWorld стабильный, вы можете использовать шаблон Visitor.

public abstract class RealObject
{
    public abstract void Accept(RealObjectVisitor visitor);
}

public class Man : RealObject
{
    public override void Accept(RealObjectVisitor visitor)
    {
        visitor.VisitMan(this);
    }
}

public class Woman : RealObject
{
    public override void Accept(RealObjectVisitor visitor)
    {
        visitor.VisitWoman(this);
    }
}

public abstract class RealObjectVistor
{
    public abstract void VisitMan(Man man);
    public abstract void VisitWoman(Woman woman);        
}


public class VirtualObjectFactory
{
    public VirtualObject Create(RealObject realObject)
    {
        Visitor visitor = new Visitor();
        realObject.Accept(visitor);
        return visitor.VirtualObject;
    }  

    private class Visitor : RealObjectVistor
    {  
        public override void VisitMan(Man man)
        {
            VirtualObject = new ManVirtualObject(man);
        }

        public override void VisitWoman(Woman woman)
        {
            VirtualObject = new WomanVirtualObject(woman);
        }

        public VirtualObject VirtualObject { get; private set; }
    }   
}
0 голосов
/ 03 мая 2011

Вы можете создавать классы Facade, которые принимают ваш объект реального мира в качестве аргумента конструктора (например, ManFacade, WomanFacade и т. Д.)

...