Я немного обдумал этот вопрос и нашел альтернативное решение. Это может быть немного неортодоксальным и анти-объектно-ориентированным, но если вы не унывают, читайте дальше ...
Опираясь на пример Apple: класс Apple может содержать много свойств, эти свойства можно разделить на связанные группы. В качестве примера я использовал класс Apple с некоторыми свойствами, относящимися к семенам яблока, а другие - с кожей яблока.
- Apple
а. Seed
a1. GetSeedCount
a2. ...
б. Кожа
b1. GetSkinColor
Би 2. ...
Я использую объект словаря для хранения всех свойств яблока.
Я написал методы расширения для определения методов доступа к свойствам, используя разные классы для их разделения и организации.
Используя словарь для свойств, вы можете перебирать все свойства, сохраненные таким образом, в любой момент (если вам нужно проверить все из них, поскольку это звучит так, как вам нужно в вашем методе обновления). К сожалению, вы теряете строгую типизацию данных (по крайней мере, в моем примере я это сделал, потому что я использую словарь . У вас могут быть отдельные словари для каждого необходимого типа, но для маршрутизации свойства потребуется больше кода). доступ к правильному словарю.
Использование методов расширения для определения средств доступа к свойствам позволяет разделить код для каждой логической категории свойств. Это сохраняет вещи организованными в отдельные части связанной логики.
Вот пример, который я придумал, чтобы проверить, как это будет работать, со стандартным предупреждением о том, что если вы продолжите идти по этому пути, произойдет робустификация (проверка, обработка ошибок и т. Д.).
Apple.cs
namespace ConsoleApplication1
{
using System.Collections.Generic;
using System.Text;
public class Apple
{
// Define the set of valid properties for all apple objects.
private static HashSet<string> AllowedProperties = new HashSet<string>(
new string [] {
"Color",
"SeedCount"
});
// The main store for all properties
private Dictionary<string, string> Properties = new Dictionary<string, string>();
// Indexer for accessing properties
// Access via the indexer should be restricted to the extension methods!
// Unfortunately can't enforce this by making it private because then extension methods wouldn't be able to use it as they are now.
public string this[string prop]
{
get
{
if (!AllowedProperties.Contains(prop))
{
// throw exception
}
if (Properties.ContainsKey(prop))
{
return this.Properties[prop];
}
else
{
// TODO throw 'property unitialized' exeception || lookup & return default value for this property || etc.
// this return is here just to make the sample runable
return "0";
}
}
set
{
if (!AllowedProperties.Contains(prop))
{
// TODO throw 'invalid property' exception
// these assignments are here just to make the sample runable
prop = "INVALID";
value = "0";
}
this.Properties[prop] = value.ToString();
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (var kv in this.Properties)
{
sb.AppendFormat("{0}={1}\n", kv.Key, kv.Value);
}
return sb.ToString();
}
}
}
AppleExtensions.cs
namespace AppleExtensionMethods
{
using System;
using ConsoleApplication1;
// Accessors for Seed Properties
public static class Seed
{
public static float GetSeedCount(this Apple apple)
{
return Convert.ToSingle(apple["SeedCount"]);
}
public static void SetSeedCount(this Apple apple, string count)
{
apple["SeedCount"] = count;
}
}
// Accessors for Skin Properties
public static class Skin
{
public static string GetSkinColor(this Apple apple)
{
return apple["Color"];
}
public static void SetSkinColor(this Apple apple, string color)
{
apple["Color"] = ValidSkinColorOrDefault(apple, color);
}
private static string ValidSkinColorOrDefault(this Apple apple, string color)
{
switch (color.ToLower())
{
case "red":
return color;
case "green":
return color;
default:
return "rotten brown";
}
}
}
}
Вот тест-драйв:
Program.cs
namespace ConsoleApplication1
{
using System;
using AppleExtensionMethods;
class Program
{
static void Main(string[] args)
{
Apple apple = new Apple();
apple.SetSkinColor("Red");
apple.SetSeedCount("8");
Console.WriteLine("My apple is {0} and has {1} seed(s)\r\n", apple.GetSkinColor(), apple.GetSeedCount());
apple.SetSkinColor("green");
apple.SetSeedCount("4");
Console.WriteLine("Now my apple is {0} and has {1} seed(s)\r\n", apple.GetSkinColor(), apple.GetSeedCount());
apple.SetSkinColor("blue");
apple.SetSeedCount("0");
Console.WriteLine("Now my apple is {0} and has {1} seed(s)\r\n", apple.GetSkinColor(), apple.GetSeedCount());
apple.SetSkinColor("yellow");
apple.SetSeedCount("15");
Console.WriteLine(apple.ToString());
// Unfortunatly there is nothing stopping users of the class from doing something like that shown below.
// This would be bad because it bypasses any behavior that you have defined in the get/set functions defined
// as extension methods.
// One thing in your favor here is it is inconvenient for user of the class to find the valid property names as
// they'd have to go look at the apple class. It's much easier (from a lazy programmer standpoint) to use the
// extension methods as they show up in intellisense :) However, relying on lazy programming does not a contract make.
// There would have to be an agreed upon contract at the user of the class level that states,
// "I will never use the indexer and always use the extension methods!"
apple["Color"] = "don't panic";
apple["SeedCount"] = "on second thought...";
Console.WriteLine(apple.ToString());
}
}
}
Адресация вашего комментария от 7/11 (дата, а не магазин):)
В приведенном вами примере кода есть комментарий, который гласит:
"Как видите, я не могу позвонить
BasicBroodmother методы на "монстра"
Вы понимаете, что можете сделать что-то подобное в этот момент:
BasicBroodmother bm = monster as BasicBroodmother;
if (bm != null)
{
bm.Eat();
}
В вашем коде не так много мяса (я понимаю, что это всего лишь пример), но когда я смотрю на него, у меня возникает ощущение, что вы сможете улучшить дизайн. Моей непосредственной мыслью было создание абстрактного класса для broodmother, который будет содержать реализации по умолчанию любых атрибутов / действий, которые являются общими для всех broodmothers. Тогда специализированные матери-потомки, такие как магическая мать-мать, будут содержать любые специализированные атрибуты / действия, специфичные для магической семьи-матери, но также наследовать от абстрактного класса и при необходимости переопределять необходимые базовые атрибуты / действия.
Я бы взглянул на шаблон Стратегии для проектирования действий, чтобы такие действия (например, поведение, как "съесть", "порождение", "атака") можно было менять в зависимости от типа монстра.
[редактировать 7/13]
Сейчас у меня нет времени вдаваться в детали (нужен сон), но я собрал пример кода , демонстрирующий другой подход.
Код состоит из:
- Broodfather.cs - абстрактный класс, заполненный всеми вещами, общими для разных типов "Broodfathers".
- BasicBroodFather.cs - конкретный класс, наследуемый от Broodfather.
- BroodfatherDecorator.cs - абстрактный класс, наследуемый всеми декораторами Broodfather.
- MagicalBroodfather.cs - этот класс украшает / оборачивает Broodfather "магией"
- BloodthirstyBroodfather.cs - этот класс украшает / оборачивает Отца кровопролитием
- program.cs - демонстрирует два примера: первый начинается с простого отца, который окутывается магией, а затем кровожадностью. Второй начинается с простого отца и оборачивает его кровью в другом порядке, затем магией.