Перегрузка иерархией классов — большинство производных не используется

#c# #overloading #class-hierarchy

#c# #перегрузка #иерархия классов

Вопрос:

Проблема

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

 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 (вероятно, поддерживая оба для компьютеров младшего уровня).

Комментарии:

1. Почему вы против того, чтобы RealObjects имели ссылку на изображение?

2. Чтобы соответствовать «единой ответственности». Можно утверждать, что объект должен знать, как он выглядит, но это не дает никакой пользы классу (если он не действительно уродлив, тогда он, возможно, захочет знать ..?).

Ответ №1:

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

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

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

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

Ответ №2:

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

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

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

Комментарии:

1. В контексте игры на XNA это, вероятно, не самая лучшая идея.

2. Это может быть правдой. Однако .NET выполняет значительную внутреннюю оптимизацию (например, кэширование сайта). Я рекомендую использовать профилировщик, чтобы увидеть, является ли это узким местом в производительности.

3. Почему бы просто не использовать универсальный метод?

4. Это не работает, выдается сообщение об ошибке: не удается найти один или несколько типов, необходимых для компиляции динамического выражения. Вам не хватает ссылок на Microsoft. CSharp.dll и System.Core.dll. Выполнение чего-то вроде «dynamic ent = entity» работает, но если вы попытаетесь передать этот ent в метод, вы получите ту же ошибку.

Ответ №3:

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

Комментарии:

1. Я не понимаю, как здесь будет работать шаблон фасада? Пожалуйста, приведите пример.

Ответ №4:

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

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

Комментарии:

1. Да, если бы getImage находился внутри RealObject, то он работал бы корректно с использованием переопределения (а не перегрузки), и моей причиной не делать этого действительно был идеал единой ответственности. Render вызывается VirtualWorld (который содержит только ссылку на RealWorld).

Ответ №5:

Если иерархия 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; }
    }   
}
  

Комментарии:

1. шаблон ehh. Visitor не предназначен для создания объектов. Это для обхода их