2015-02-14 3 views
6

Я стараюсь придерживаться хороших принципов проектирования и дизайна дизайна и т. Д. Поэтому, разрабатывая это приложение на C#, я часто могу найти несколько решений для проектирования и архитектуры, я всегда хочу найти и реализовать более «канонический» в надежде создать высокоподдерживаемое и гибкое программное обеспечение, а также стать лучшим программистом OO ,Обеспечение безопасности типов унаследованных элементов в унаследованных классах

Предположим, у меня есть эти два абстрактных класса: Character и Weapon.

abstract class Weapon 
{ 
    public string Name { get; set; } 
} 

abstract class Character 
{ 
    public Weapon weapon { get; set; } 
} 

Производные классы могут быть Sword и Staff от Weapon и Warrior и Mage от Character и т.д. (это все гипотетических, не имеет отношение к моему реальному программному обеспечению!).

Каждый Character имеет Weapon, но для каждой реализации Character Я знаю, что реализация Weapon она будет иметь. Например, я знаю (и я хочу обеспечить соблюдение!), Что во время выполнения каждый экземпляр Warrior будет иметь Weapon типа Sword. Конечно, я мог бы сделать это:

class Sword : Weapon 
{ 
    public void Draw() { } 
} 

class Warrior : Character 
{ 
    public Warrior() 
    { 
     weapon = new Sword(); 
    } 
} 

Но этот путь, каждый раз, когда я хочу использовать мой Weapon объект правильно внутри Warrior, я должен выполнить бросок, который я считаю, чтобы быть не столь большой из практика. Кроме того, у меня нет никаких средств, чтобы помешать мне испортиться, то есть нет безопасности типа!

Идеальным решением было бы иметь возможность переопределить Weapon weapon свойство в Warrior классе с Sword weapon собственности, таким образом у меня есть безопасность типа, и если пользователь использует мой Warrior как Character, он все еще может использовать свой Sword, как a Weapon. К сожалению, похоже, что C# поддерживает такую ​​конструкцию.

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

Спасибо за чтение!

+0

«каждый раз, когда я хочу использовать свой объект оружия должным образом» - можете ли вы привести пример того, что не является «правильным»? – mungflesh

+0

Правильное значение без необходимости приведения. Разве вы не согласитесь, что если каждый раз, когда я использую объект, я должен его бросить, в дизайне что-то не так? – WHermann

+0

Согласен, но я не понимаю, почему вам нужно бросить. – mungflesh

ответ

12

ОБНОВЛЕНИЕ: Этот вопрос был источником вдохновения для a series of articles on my blog. Спасибо за интересный вопрос!

это какая-то классическая проблема с OO, с именем и хорошо документированным решением?

Основная проблема, которую вы бежите в здесь, что трудно в системе ОО добавить ограничение к подкласса. Говоря «персонаж может использовать оружие», а затем «воин - это своего рода характер, который может использовать только меч», трудно моделировать в системе OO.

Если вас интересует теория, стоящая за этим, вам нужно искать «Принцип замещения Лискова». (А также любое обсуждение вопроса о том, почему «Квадрат» не является подтипом «Прямоугольник» и наоборот.)

Есть, однако, способы сделать это. Вот он.

interface ICharacter 
{ 
    public IWeapon Weapon { get; } 
} 
interface IWeapon { } 
class Sword : IWeapon { } 
class Warrior : ICharacter 
{ 
    IWeapon ICharacter.Weapon { get { return this.Weapon; } } 
    public Sword Weapon { get; private set; } 
} 

Теперь площадь поверхности общественного Воина есть оружие, которое всегда Sword, но кто-нибудь с помощью интерфейса ICharacter видит Weapon свойство, которое имеет тип IWeapon.

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

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

Это становится беспорядком. Рассмотрим не, пытаясь захватить слишком много в системе типов.

+0

Большое спасибо, похоже, мне придется переосмыслить пару вещей. Не могли бы вы сказать, что C# держит меня здесь? Я имею в виду, знаете ли вы о другом языке с более мощной системой типов, которая позволила бы это сделать более естественно? (только из любопытства) – WHermann

+2

@WHermann: у C# есть довольно приличная система типа, как это происходит. Если у вас действительно есть проблема с несколькими динамическими рассылками, тогда такие языки, как Дилан, выполняют достойную работу. Если вы хотите использовать дженерики, у Scala есть немного улучшенная поддержка ковариации/контравариантности. «Высокие типы» Хаскелла также достаточно сильны. –

9

Просто используйте generics type constraint! Вот пример

public abstract class Weapon 
{ 
    public string Name { get; set; } 
} 

public interface ICharacter 
{ 
    Weapon GetWeapon(); 
} 

public interface ICharacter<out TWeapon> : ICharacter 
    where TWeapon : Weapon 
{ 
    TWeapon Weapon { get; } // no set allowed here since TWeapon must be covariant 
} 

public abstract class Character<TWeapon> : ICharacter<TWeapon> 
    where TWeapon : Weapon 
{ 
    public TWeapon Weapon { get; set; } 
    public abstract void Fight(); 
    public Weapon GetWeapon() { return this.Weapon; } 
} 

public class Sword : Weapon 
{ 
    public void DoSomethingWithSword() 
    { 
    } 
} 

public class Warrior : Character<Sword> 
{ 
    public override void Fight() 
    { 
     this.Weapon.DoSomethingWithSword(); 
    } 
} 
+0

Интересно! вы бы сказали, что это идиоматическое использование дженериков в C#? – WHermann

+0

Возможно, я не совсем понимаю, что вы подразумеваете под «идиоматическим», но дженерики не характерны для C#. Вы также можете найти дженерики в Java или на C++ (и многие другие языки, о которых я не знаю), но это синтаксис C#. – rducom

+0

Ничего. Это отличный ответ, и я думаю, что это может решить проблему, спасибо! – WHermann

1

Ради документации и в будущем, я отправляю решение, что я, скорее всего, в конечном итоге с помощью:

abstract class Weapon {} 

abstract class Character 
{ 
    public abstract Weapon GetWeapon(); 
} 

class Sword : Weapon {} 

class Warrior : Character 
{ 
    public Sword Sword { get; private set; } 

    public override Weapon GetWeapon() 
    { 
     return Sword; 
    } 
} 

Как я понимаю, единственным недостатком является то, что объекты типа Воина есть a Sword Sword Недвижимость и a Weapon GetWeapon() способ. В моем случае это происходит как косметическая проблема только потому, что и свойство, и метод возвращают ссылку на тот же объект при вызове.

Любые комментарии приветствуются!

+0

Почему не свойство 'Weapon' get, а не' GetWeapon() 'метод, в классе' Character'? – drowa

+0

@drowa Нет особой причины, я думаю, что либо будет работать так же. – WHermann

1

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

У вас есть «персонажи вещи, которые имеют оружие»

abstract class Character { 
    public Weapon weapon { get; set; } 
} 

но то, что вы действительно хотите, чтобы сказать что-то вроде «персонажей есть вещи, которые имеют оружие, но с некоторым ограничением».Я хотел бы предложить, не имеющие конкретных типов, как «воин» и «меч» на всех, а те конкретные типы должны быть экземпляры:

sealed class Weapon { 
    public string Name { get; set; } 
} 

sealed class Character { 
    const int sSkillToUseWeapon = 50; 
    readonly Dictionary<string, int> mWeaponSkills = new Dictionary<string, int>(); 
    Weapon mCurrentWeapon; 

    public Weapon CurrentWeapon { 
     get { 
     return mCurrentWeapon; 
     } set { 
     if (!mWeaponSkills.ContainsKey(value.Name)) 
      return; 

     if (mWeaponSkills[value.Name] < mSkillToUseWeapon) 
      return; 

     mCurrentWeapon = value; 
     } 
    } 

    public void SetWeaponSkill(string pWeaponName, int pSkill) { 
     if (mWeaponSkills.ContainsKey(pWeaponName)) 
     mWeaponSkills[pWeaponName] = pSkill; 
     else 
     mWeaponSkill.Add(pWeaponName, pSkill); 
    } 
} 

Тогда вы можете объявить их экземпляры для использования и повторного использования:

var warrior = new Character(); 
warrior.SetWeaponSkill("Sword", 100); 
warrior.SetWeaponSkill("Bow", 25); 

var archer = new Character(); 
archer.SetWeaponSkill("Bow", 125); 
archer.SetWeaponSkill("Sword", 25); 
archer.SetWeaponSkill("Dagger", 55); 
//etc... 

A Warrior - это случай персонажа с определенным набором навыков оружия.

Когда вы подходите к этому пути, еще одна проблема, с которой вы связаны, заключается в том, что существует разница между, например, «Ржавым мечом» и «Ржавым мечом». Другими словами, даже если «Ржавый меч» является экземпляром «Оружия», по-прежнему существует разница между общей концепцией «Ржавый меч» и настоящим ржавым мечом, который лежит на земле. То же самое для «Гоблина» или «Воина».

Термины, которые я использую для себя: «Stock____» и «Размещено ____», поэтому существует разница между StockWeapon, который определяет «класс» оружия, и «РазмещенныйWeapon», который является фактическим «экземпляром» StockWeapon в игровом мире.

Размещенные объекты «имеют» экземпляры объектов запаса, которые определяют их общие атрибуты (базовый урон, базовую прочность и т. Д.) И имеют другие атрибуты, которые определяют их текущее состояние независимо от других подобных предметов (местоположение, долговечность , и т. д.)

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

+0

Теперь я очень сожалею, что использовал аналогию D & D! Это здорово, но, к сожалению, не относится к моей реальной проблеме. Anyways, +1 для ссылки, я очень наслаждаюсь любыми шаблонами OO! – WHermann

+0

Можете ли вы уточнить венгерскую нотацию? s ... m ... p ...? Я не думаю, что видел их раньше! – WHermann

+0

@WHermann i use s, m, p для статического члена экземпляра, параметр, соответственно –

0

Я думаю, что вы испытываете недостаток поддержки в C# для Covariant return type. Обратите внимание, что вы можете увидеть get свойства как особый вид метода.

Смежные вопросы