В пятницу на очередном занятии возник вопрос - когда лучше делать подклассы, а когда можно обойтись просто свойством (например, сделать тип сущности)? Я так думаю, что если отличий в разных типах не много, то мутить с подклассами не стоит.
Например, если кирпичи в игре Арканоид отличаются только цветом и числом попаданий до уничтожения, то вполне можно обойтись либо типом и тогда возвращать соответсвующие свойства в зависимости от типа:
public Color Foreground()
{
swith (type)
{
case StoneType.One: return Colors.Red;
............
}
Либо вообще вынести эти параметры в конструктор и типы не заводить:
public Stone(Color foreground, int countStrike)
{
.....
}
А вот если отличий будет очень много, то класс будет замусорен кучей if или switch. Вот тогда имеет смысл делать подклассы и разносить логику и код. Разумеется, если какой-то из "типов" имеет дополнительные свойства, то это однозначно нужно выностить в подкласс.
Saturday, February 26, 2011
Monday, February 21, 2011
Еще пример про делегаты
Задача такая: по имени функции (математической) получить результат ее вычисления. Самый простой вариант такой:
public class CallFunc1
{
/// <summary>
/// Вызываем функцию по имени
/// </summary>
public double GetFuncValue(string funcName, double arg)
{
switch (funcName.ToUpper())
{
case "SIN": return Math.Sin(arg);
case "COS": return Math.Cos(arg);
case "TAN": return Math.Tan(arg);
case "SQRT": return Math.Sqrt(arg);
}
throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
}
}
Но тут мы вспоминаем, что квадратный корень нельзя вычислять от отрицательного значения и добавляем еще код:
case "SQRT":
if (arg < 0)
throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
return Math.Sqrt(arg);
Потом появляются еще функции и свои проверки… В результате метод GetFuncValue напоминает сборную солянку. А хотелось бы как-то попроще и покрасивее сделать.
С помощью делегатов можно сделать так. Описываем делегат, имеющий сигнатуру метода с одним параметром:
// Делегат- математический метод
public delegate double MathFunction(double arg);
А теперь описываем словарь соответствия названий и делегатов таких методов:
// Список функций
private Dictionary<string, MathFunction> funcList;
Инициализировать его нужно будет в конструкторе:
/// <summary>
/// Конструктор
/// </summary>
public CallFunc1()
{
// Создаем список функций
funcList = new Dictionary<string, MathFunction>();
funcList.Add("SIN", delegate(double arg) {
return Math.Sin(arg);
});
funcList.Add("COS", delegate(double arg)
{
return Math.Cos(arg);
});
funcList.Add("TAN", delegate(double arg)
{
return Math.Tan(arg);
});
funcList.Add("SQRT", delegate(double arg)
{
if (arg < 0)
throw new ArgumentException("Попытка вычислить квадратный корень от отрицательного числа", "arg");
return Math.Sqrt(arg);
});
}
Обратите внимание, что реализация делегата сделана прямо “по месту” (это называется анонимными методами – методами без имени).
А с помощью LINQ вызов станет совсем простым делом:
public double GetFuncValue(string funcName, double arg)
{
// Ищем нужную функцию в списке
var func = funcList.Where(f => f.Key == funcName).Select(f => f.Value).FirstOrDefault();
// Не нашли - генерируем исключение
if (func == null)
throw new ArgumentException("Такой функции нет в списке зарегистрированных функций", funcName);
// Нашли - вызываем эту функцию
return func(arg);
}
Теперь каждое имя функции связанно со своей реализацией и своими ограничениями.
Делегаты
В C# нет указателей (про unsafe я молчу). И это хорошо и правильно – указатель он по сути своей не типизирован. Передали в метод ссылку, а как ее интерпретировать – на совести принимающей стороны. Можем передать int, а использовать как float. И т.д. Тоже самое касается и методов – передали указатель на метод с одной сигнатурой, а используем совсем по-другому…
Зато в C# есть делегаты. Я бы назвал делегаты “типизированные указатели на методы”. Покажу на примере как это работает.
Сначала нужно описать тип делегата, т.е. сигнатуру метода. Описание можно не привязывать к конкретному классу:
namespace DelegateTest
{
public delegate decimal GetVarValue(string name);
Здесь метод имеет один параметр типа string и возвращает decimal.
Теперь этот делегат мы можем использовать при описании параметров и переменных:
public class Parser
{
/// <summary>
/// Описание переменной, сохращающей делегат
/// </summary>
private GetVarValue getVarValueHandler;
/// <summary>
/// Передаем делегат в конструктор
/// </summary>
/// <param name="getVarValueHandler"></param>
public Parser(GetVarValue getVarValueHandler)
{
// Сохраняем делегат, т.к. хотим использовать его
// в другом методе этого класса
this.getVarValueHandler = getVarValueHandler;
}
/// <summary>
/// Метод расчета
/// </summary>
/// <returns></returns>
public decimal Calc()
{
decimal result = 0;
// Вызываем делегат с параметром var1
result += getVarValueHandler("var1");
// Вызываем делегат с параметром var2
result += getVarValueHandler("var2");
return result;
}
Описание переменной типа делегат выглядит совершенно так же как и обычной переменной. Переменная getVarValueHandler имеет тип GetVarValue, т.е. тип делегата с нашей сигнатурой. Отличие от переменной в том, что мы можем вызывать этот метод. Разумеется, вызывать его можно только так, как он описан, т.е. с одним параметром типа string:
getVarValueHandler("var1");
Прелесть в том, что никак иначе вызвать метод по этой ссылке (а передали мы фактически ссылку на метод) нельзя. Только так, как метод описан в описании делегата.
Использовать этот класс тоже очень просто. Во-первых, нам потребуется метод, который мы будем передавать в конструктор нашего класса. Наверное, излишне говорить, что сигнатура этого метода должна совпадать с сигнатурой делегата и передать в конструктор что-то другое нельзя:
class Program
{
static void Main(string[] args)
{
// Передаем в конструктор делегат, т.е. ссылку
// на наш метод
Parser parser = new Parser(GetVarValueMethod);
// Вызываем метод Calc
Console.WriteLine(parser.Calc());
}
/// <summary>
/// Делегат. Имя у него может быть любое, главное
/// чтобы сигнатура была той как описана в типе
/// делегата
/// </summary>
static decimal GetVarValueMethod(string name)
{
Console.WriteLine("Value of {0}:", name);
return decimal.Parse(Console.ReadLine());
}
}
Имя метода может быть может быть любое, главное чтобы сигнатура совпадала с сигнатурой делегата.
Вот и все. Теперь при вызове метода Calc будет два раза вызван метод GetVarValueMethod.
Цитата
студенты: а что вы обычно говорите своим выпускникам, когда встречаете их?
преподаватель: картошку фри и большую колу пожалуйста
преподаватель: картошку фри и большую колу пожалуйста
Про автомобили и заказчиков
Стал замечать, что очень часто перевожу объяснения нашей работы на примеры более понятые для заказчиков. Например – про автомобили. Объяснять про билды, сборки, тестирование – сложно, а про СТО, автомобили и креш-тесты – как-то ближе получается, проще…
Про олимпиадные задачи и водителей недавно писал… Не для заказчиков правда, для студентов, но все равно как-то доходчивей получается, чем про код и процесс разработки…
Как-то несколько дней просил заказчика прислать нам новые требования. Уже все старые почти сделали, а новых все нет, но точно обещано, что будут. Объясняю – вот если автомобиль разобрали на СТО, чтобы масло поменять в коробке, висит он на подъемнике, поменяли, собрали. И тут вы приходите и говорите что надо бы еще диск сцепления заменить – ну ведь это же все заново разбирать, снова собирать… И мастер ругается словами неприличными и вам потом сборка-разборка в счет пойдет… Другое дело, если заранее сказать, пока все разобрано. Делов-то пару гаек открутить… Так и у нас – пока билд не собрали, пока не оттестировали все – изменения еще можно вносить, а потом это уже двойная работа будет. Надо сказать, что такое объяснение “прошло” – требования прислали.
Monday, January 31, 2011
Цитата
40 музыкантов симфонического оркестра играют симфонию за 30 минут.
За какое время ту же симфонию сыграют 80 музыкантов?
За какое время ту же симфонию сыграют 80 музыкантов?
Saturday, January 22, 2011
Еще раз про пользу правильного именования
В старой ветке RDSN форума обсуждалось как-то написание пословиц на C++. Вот так например:
if (flag == true)
if (flag == true)
if (flag == true)
if (flag == true)
if (flag == true)
if (flag == true)
if (flag == true)
Cut();
Скороговорка:
greka.drive(river,moveType::Over);if(greka.lookUpFirstItem(river) isinstanceof Cancer){ river.insert(greka.hand); crayfish.grab(greka.hand);}И еще про рыбу:
bool IsFish(const Animal &animal)
{
...
if(FishContainer.IsEmpty && IsCrawfish(animal))
return true;
...
}
А вот довольно спорная по сути, но зато оформленная двумя классами:
class CBaba : public CHomoSapiens{public: bool KonyaNaSkakuOstanovit(); bool VGoryazhuyIzbuVoidet();}class CKobila : public CAnimal{private: int m_trudno;public: bool BabaSVozu( const CBaba&) { m_trudno--; // Kobile legche }}Шутки-шутками, а если серьезно – подумайте, ведь правильное именование переменных и методов позволяет именно читать код, понимать его суть, т.е. бизнес-смысл! И в реальном коде должно быть тоже самое.
Subscribe to:
Posts (Atom)