С++, интерфейсы
соответственно тебе должно быть похер как твою функцию будут использовать
Пробовать делать dynamic_cast на результат?вот это классический кошерный способ
ps
getType и заглушка для getRainIntensity - не рекомендуются, но в ряде случаев могут использоваться
я знаю. Проблема в том, что у меня есть функция, которая может вернуть как базовый класс, так и потомка. У потомка могут быть методы, которых нет у предка или других потомков. Как мне до того метода добраться?
кастуй
Пример, кстати, неудачный, потому что getRainIntensity имеет смысл для любой погоды - просто обычно он почти равен нулю. Перенеси его в предка.
С getRainIntensity пример действительно плохой, тут можно и заглушку написать.
GetType реализовывать явно хуже, чем юзать dynamic_cast в том сценарии, что пишешь ты.
Следи, чтобы этими кастами не загнать себя в swich/case'ы, их сложно поддерживать. Надо решить, будет ли твоя иерархия типов погоды расширяемой, от этого много зависит. Например, стоит ли пользователю давать возможность добавлять свои производные типы погоды? И планируется ли дальнейшее расширение уже имеющейся
иерархии.
Вообще хочется сказать, что в таких случаях как погода не стоит использовать наследование вообще, но не буду, потому что не знаю как ты собираешься это использовать.
Проблема в том, что у меня есть функция, которая может вернуть как базовый класс, так и потомка. У потомка могут быть методы, которых нет у предка или других потомков. Как мне до того метода добраться?
тебе нужно этот метод добавить в базовый абстрактный класс, либо добавить в базовый абстрактный класс метод, с помощью которого ты сможешь идентифицировать объекты, как в COM-е.
Иначе зачем тебе тогда вообще интерфейс ?
abstract class Lookup
{
public:
virtual void* getObject(char* type);//или std::string type
}
abstract class SimpleWeather:Lookup
{
public:
virtual int getTemperature;
}
abstract class RainyInfo
{
public:
virtual double getRainIntensity;
}
class RainyWeather:SimpleWeather,RainyInfo
{
public:
//тут за правильность синтаксиса не ручаюсь.
override double getRainIntensity{return 0;}
override void* getObject(char* type){
//здесь можно использовать map.
//можно разрешить добавлять/удалять возможности динамически, подгружать их из плагинов-dll'ек имена которых находятся в каком-нибудь xml-нике и.т.д.
if (strcmp(type,"RainyInfo")==0)
{
return &(RainyInfo)this;
}
return NULL;
}
}
Еще есть Netbeans way:
Это вариант COM-way, только в СОМе вместо строк GUIDы, и подсчет ссылок для управления временем жизни.
Как мне правильно реализовать и использовать библиотеку? Пробовать делать dynamic_cast на результат? Добавить метод getType? Добавить в предка simpleWeather заглушку для getRainIntensity?У тебя стоит задача конкретизировать абстракцию. Она решается несколькими способами, каждый из которых зависит от того, как в будущем будет расширяться код.
Если у тебя только одна операция с погодой (например, надо просто её проверить и получить какие-то параметры то подходит как dynamic_cast, так и метод getType. В общем-то они эквивалентны с точки зрения проектирования.
Если будет много операций, требующих конкретизации типа погоды, то в этом случае надо использовать паттерн Visitor. Это одна из областей его применения.
dynamic_cast, и метод getType. В общем-то они эквивалентны с точки зрения проектирования.Ну что значит эквивалентны? Нет. Если у тебя есть getType, то ты пользователю сразу раскрываешь полную информацию о себе, если же делать только dynamic_cast, то клиент может только проверить, поддерживает ли данный объект некоторый интерфейс. Это более безопасно.
Вообще еще раз выскажусь, что не стоит использовать наследование. Но если некоторые типы погоды действительно должны быть как-то по особому _реализованы_, то может пригодиться паттерн State.
Ну что значит эквивалентны? Нет. Если у тебя есть getType, то ты пользователю сразу раскрываешь полную информацию о себе, если же делать только dynamic_cast, то клиент может только проверить, поддерживает ли данный объект некоторый интерфейс. Это более безопасно.Под функцией getType понималась не GetType из C#, а обычная функция, которая возвращает какой-нибудь enum. И если этот enum равен rainy_weather, то ты можешь смело приводить указатель к нужному тебе типу. Это несколько проще, чем делать несколько dynamic_cast в поисках конкретики. С точки зрения проектирования эти решения эквивалентны.
Но если некоторые типы погоды действительно должны быть как-то по особому _реализованы_, то может пригодиться паттерн State.Ни одно из применений этого паттерна не подходит к поставленной задаче.
Под функцией getType понималась не GetType из C#, а обычная функция, которая возвращает какой-нибудь enum. И если этот enum равен rainy_weather, то ты можешь смело приводить указатель к нужному тебе типу. Это несколько проще, чем делать несколько dynamic_cast в поисках конкретики. С точки зрения проектирования эти решения эквивалентны.Это пример грубейшей ошибки проектирования: ты дублируешь иерархию в еще каком-то enum'е, это приведет к апокалипсису: ты создаешь еще один производный класс, но у тебя нет доступа к определению enum'а: что ты будешь возвращать в соответствующем методе?
Ни одно из применений этого паттерна не подходит к поставленной задаче.Конечно же подходит. Ты где прочитал, что не подходит? Я применял этот паттерн в сходной ситуации и все были довольны.
вариант 1:
class SimpleWeather
{
public:
virtual ~SimpleWeather {}
virtual int getTemperature const = 0;
virtual double getRainIntensity const = 0;
};
std::auto_ptr<SimpleWeather> getCurrentWeather;
вариант 2:
class SimpleWeather
{
public:
virtual ~SimpleWeather {}
virtual int getTemperature const = 0;
};
class RainyWeather
{
public:
virtual ~RainyWeather {}
virtual double getRainIntensity const = 0;
};
std::auto_ptr<SimpleWeather> getCurrentWeather;
std::auto_ptr<RainyWeather> getCurrentRainyWeather;
никаких кастов и свитчей
ребята, пример высосан из пальца "погода" это должна быть struct, что там от кого инкапсулировать за интерфейсами не понимаю. давайте нормальный пример можно будет что-нибудь пообсуждать конструктивнее.
Это пример грубейшей ошибки проектирования: ты дублируешь иерархию в еще каком-то enum'е, это приведет к апокалипсису: ты создаешь еще один производный класс, но у тебя нет доступа к определению enum'а: что ты будешь возвращать в соответствующем методе?Это не пример грубейшей ошибки проектирования, а пример решения задачи. Иерархии в enum-е не будет, там будут перечислены все подтипы. Для расширяемости можно возвращать строку или guid, но опять же, с точки зрения проектирования это параллельно. Здесь незачем вдаваться в технические детали реализации, а то придётся дойти до момента, что фабрика классов, создающая конкретные типы, тоже должна быть расширяемой извне. (И продолжить тем, что в правильных языках это сделать в порядки проще, чем в C++/C#/Java.)
В общем-то описанная проблема с enum будет возникать в том или ином виде и в Visitor. Ровно как и при полном дублировании всех интерфейсов с заглушечными реализациями - добавление нового конкретного класса может привести к требованию добавить новую функцию в самый главный интерфейс.
Конечно же подходит. Ты где прочитал, что не подходит? Я применял этот паттерн в сходной ситуации и все были довольны.Я прочитал это в книжке от GoF. Ты напиши применение этого паттерна в этой конкретной ситуации и сразу увидишь проблемы.
В коде с паттерном State интересует, например, операция, которая зависит от того, дождливая сейчас погода или нет.
ребята, пример высосан из пальца "погода" это должна быть struct, что там от кого инкапсулировать за интерфейсами не понимаю. давайте нормальный пример можно будет что-нибудь пообсуждать конструктивнее.Мы понимаем, что пример высосан из пальца. Например, погода может быть ещё и ветренной и в этом случае и дождливую и обычную погоду придётся разбить на два подкласса. Про то, что наследованием плохо решать "погодную" задачу выше уже написали.
Я рассматриваю пример автора как проблему конктеризации абстракции, и уже привёл одно решение из книжки - это паттерн Visitor. Но на самом деле для проблемы с проектированием классов погоды можно подобрать более хорошее решение, где класс погоды будет один, а абстрактными будут, например, свойства этой погоды.
"Погода --- состояние тропосферы," география, 5-й класс
среднеобразовательной школы, а как ты моделируешь состояние,
это уже другой вопрос, хоть алгебраическими типами.
---
"Нахер нужны выпускники мехмата, если они за третий класс
математику не знают... Мозг высшей геометрией разрушен."
apafelak
как проблему конктеризации абстракции, и уже привёл одно иp решений из книжки - это паттерн Visitor.пример приведи, пожалуйста.
пример приведи, пожалуйста.Если тебе требуется код, то он есть в Wikipedia. Впрочем, там описан Hierarchical Visitor, а ты почему-то не признаёшь реализацию с иерархией классов без коллекций. (Кстати там пример не очень удачный с той точки зрения, что обход коллекции следовало бы реализовать внутри элемента.)
Если тебе требуется ссылка, откуда взялось моё утверждение, то могу привести цитату из GoF: "an object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes".
Если тебе требуется код, то он есть в Wikipedia. Впрочем, там описан Hierarchical Visitor, а ты почему-то не признаёшь реализацию с иерархией классов без коллекций. (Кстати там пример не очень удачный с той точки зрения, что обход коллекции следовало бы реализовать внутри элемента.)какое отношение - это все имеет - к "конкретизации абстракции"?
Если тебе требуется ссылка, откуда взялось моё утверждение, то могу привести цитату из GoF: "an object structure contains many classes of objects with differing interfaces, and you want to perform operations on these objects that depend on their concrete classes".
вот допустим есть абстракция "список": даже есть его кокретизации: IEnumerable<T>, ICollection<T>, IList<T>, T[], List<T> и т.д.
а визитер здесь как применим? и как он улучшает(проявляет) эту самую "конкретизацию абстракции"?
вот допустим есть абстракция "список": даже есть его кокретизации: IEnumerable<T>, ICollection<T>, IList<T>, T[], List<T> и т.д. а визитер здесь как применим?Если тебе нужны операции над конкретными типами списка, то можно воспользоваться паттерном Visitor даже в этом случае. Только его придётся реализовать через typeof
ты про "конкретизацию абстракции" расскажи
какое отношение - это все имеет - к "конкретизации абстракции"?Я имел ввиду конкретизацию, которая требуется при выполнении операций. Обеим операциям из поста автора требуется конкретизация типа погоды:
Может, в веб-приложении отрисовываться будет, может всплытием подводной лодки командовать.
Я имел ввиду конкретизацию, которая требуется при выполнении операций. Обеим операциям из поста автора требуется конкретизация типа погоды:т.е. перевернуть поток управления?
не люблю приложения с потоком управления в виде "спагетти", а применение визитора в этой задаче - как раз это и сделает - опрокинет поток управления.
т.е. перевернуть поток управления?Ничего не понял из твоего поста. Ты про проблему с double dispatch? Да, она есть в языках C++/C#/Java. Я понимаю, что не все могут прочитать вызов инкапсулированной в какой-то класс операции, не читая содержимого этого класса. И не все могут разрабатывать такую операцию, не просматривая постоянно код всех её вызовов. Это не повод отказываться от ООП, ведь к этому подходу тоже надо привыкнуть и научиться читать и писать код немного по-другому.
не люблю приложения с потоком управления в виде "спагетти", а применение визитора в этой задаче - как раз это и сделает - опрокинет поток управления.
#include <memory>
#include <iostream>
using namespace std;
class Visitor;
struct Expr {
virtual void Accept(Visitor & v) = 0;
};
struct Const; struct Add; struct Negate;
class Visitor {
public:
virtual void visit(Const & e) = 0;
virtual void visit(Add & e) = 0;
virtual void visit(Negate & e) = 0;
void Visit(Expr & e) { e.Accept(*this); }
};
struct Const: public Expr {
int c;
Const(int c): c(c){}
virtual void Accept(Visitor & v) { v.visit(*this); }
};
struct Add: public Expr {
auto_ptr<Expr> x;
auto_ptr<Expr> y;
Add(Expr * x, Expr * y): x(x y(y) {}
virtual void Accept(Visitor & v) { v.visit(*this); }
};
struct Negate: public Expr {
auto_ptr<Expr> x;
Negate(Expr * x): x(x) {}
virtual void Accept(Visitor & v) { v.visit(*this); }
};
class Show: public Visitor {
public:
virtual void visit(Const & e) {
cout << e.c;
}
virtual void visit(Add & e) {
cout << "(";
Visit(*e.x);
cout << ")+(";
Visit(*e.y);
cout << ")";
}
virtual void visit(Negate & e) {
cout << "-(";
Visit(*e.x);
cout << ")";
}
};
class Eval: public Visitor {
public:
int state;
virtual void visit(Const & e) {
state = e.c;
}
virtual void visit(Add & e) {
Visit(*e.x);
int x = state;
Visit(*e.y);
int y = state;
state = x+y;
}
virtual void visit(Negate & e) {
Visit(*e.x);
state = -state;
}
};
int main {
auto_ptr<Expr> e(new Add(new Negate(new Add(new Const(2 new Negate(new Const(1 new Const(3;
Show.Visit(*e);
cout << endl;
Eval i;
i.Visit(*e);
cout << i.state << endl;
return 0;
}
[xoft ~]$ ./visitor
(-2)+(-(1+(3)
2
Ты про проблему с double dispatch?нет. я про другое.
вот это прямой поток управления (все принятия решений идут последовательно, в одном месте, понятно как делается прерывание или изменение потока управления)
using (var weatherStation = new WeatherStation
{
var weather = weatherStation.GetCurrentWeather;
weather.If<RainWeather>(rainWeather =>
{
Console.WriteLine("Rain: {0}", rainWeather.RainIntesitiy);
});
}
а вот "перевернутый"/"расщепленный" поток управления, поток управления в виде спагетти
void OnLoad
{
this.weatherStation = new WeatherStation;
}
void OnStart
{
this.weather = weatherStation.GetCurrentWeather;
}
void OnShow
{
weather.If<RainWeather>(rainWeather =>
{
Console.WriteLine("Rain: {0}", rainWeather.RainIntesitiy);
});
}
void OnDispose
{
weatherStation.Dispose;
}
никакого спагеттиа как в этом коде будет реализовываться знание о том, что есть бинарные, а есть унарные операции?
или знание о том, когда скобки надо ставить, а когда не надо?
т.е. визитор хорош, пока каждый визит атомарный, и не требуется передача состояния между операциями.
как только идет передача состояния - получается спагетти.
вот это прямой поток управления (все принятия решений идут последовательно, в одном месте, понятно как делается прерывание или изменение потока управления)Тогдя я не понял, какое всё это имеет отношение к Visitor. У нас есть N абстракций и растущее число операций, которые требуют конкретизации. (Собственно это и написано в цитате из GoF.)
Тогдя я не понял, какое всё это имеет отношение к Visitor. У нас есть N абстракций и растущее число операций, которые требуют конкретизации. (Собственно это и написано в цитате из GoF.)тем, что ты предлагаешь операцию
if (weather is RainWeather)
{
...
}
заменить на
void OnRainWeather
{
}
а как в этом коде будет реализовываться знание о том, что есть бинарные, а есть унарные операции?Они будут разными типами. Мало того, визиторы ещё бывают многоуровневыми. Например, сначала можно уточнить абстракцию, а затем конкретизировать.
тем, что ты предлагаешьНет, я предлагаю сделать операцию объектом и вынести её за пределы абстрактной иерархии. Приведённый тобою код не показывает ошибочности картины применения серий if/else. Как только ты начнёшь наращивать количество операций, сразу же увеличится циклическая сложность кода.
Приведённый тобою код не показывает ошибочности картины применения серий if/else. Как только ты начнёшь наращивать количество операций, сразу же увеличится циклическая сложность кода.пока не понимаю, чем код в данной ситуации с визитор-ом проще, лучше и т.д.:
и где увеличение циклической сложности
чем это хуже
SwitchOnType(weather)
.Case<RainWeather>(rainWeather =>
{
textBox.Text = "rain"; rainTextBox.Text = rainWeather.RainIntensity.ToString;
})
.Case<SunWeather>(sunWeather => {image.Image = Images.Sun;})
//и т.д.
чем
new MyVisitor(textBox, rainTextBox, rainTextBox, image, Images).Visit(weather);
class MyVisitor: IWeatherVisitor
{
public void OnRainWeather(RainWeather rainWeather)
{
textBox.Text = "rain"; rainTextBox.Text = rainWeather.RainIntensity.ToString;
}
public void OnSunWeather(SunWeather sunWeather)
{
image.Image = Images.Sun;
}
}
или ты как-то по другому предлагаешь применить визитор?
SwitchOnType(weather)Это по-твоему не увеличение циклической сложности?
.Case<RainWeather>(rainWeather =>
{
textBox.Text = "rain"; rainTextBox.Text = rainWeather.RainIntensity.ToString;
})
.Case<SunWeather>(sunWeather => {image.Image = Images.Sun;})
Как на это написать unit test для каждой операции?
Как внести в этот код состояния?
class Eval: public Visitor {кстати в Eval-е уже идет расщепление потока управление,
public
т.к. есть неявное изменение внешней(по отношению к Visit) переменной state.
т.е. даже такое было бы более наглядное, и имело бы меньшую сложность.
class Eval: public Visitor {
public:
virtual int visit(Const & e) {
return e.c;
}
virtual int visit(Add & e) {
return Visit(e.x) + Visit(e.y);
}
virtual int visit(Negate & e) {
return -Visit(*e.x);
}
};
Это по-твоему не увеличение циклической сложности?циклическая сложность между чем и чем?
Как на это написать unit test для каждой операции?это пока все одна операция, но с вариантами
соответствено на эту одну операцию тесты и будут писаться.
Как внести в этот код состояния?состояние уже есть, в виде textBox, rainTextBox и т.д.
с визитором - это состояние пришлось явно передавать через конструктор
циклическая сложность кодаА что это? Или имелась в виду цикломатическая ?
А что это? Или имелась в виду цикломатическая ?Да, косноязычность моего перевода.
ну прототип то у нас void visit(...) а не int visit(... на все случаи жизни возвращаемых типов не напасешься я как раз хотел показать что у визитора может быть состояние. и кстати сами ноды визитор тоже может апдейтить, это кажется тоже вполне нормально.
циклическая сложность между чем и чем?Просто циклическая сложность функции с операцией. Очевидно, что в switch решениях она выше, чем в double dispatch. В приведённом тобой примере каждый вызов функции Case добавляет как минимум двойку, плюс сама функция Case будет сложность больше или равную двум.
это пока все одна операция, но с вариантамиАвтор описывал задачу с несколькими операциями. Каждую из них нужно будет протестировать. А одну функцию с высокой циклической сложностью протестировать труднее, чем N функций с низкой сложностью. Для полного покрытия придётся тестировать как то, что Case вызывает каждый конкретный случай, так и то, что он НЕ вызывает его. В таком случае стандартный case + enum будет даже предпочтительнее, потому что его циклическая сложность будет в два раза ниже.
соответствено на эту одну операцию тесты и будут писаться.
состояние уже есть, в виде textBox, rainTextBox и т.д.А в твоём случае ты его передал не через конструктор? Или ты не инкапсулируешь операцию? В таком случае мне интересно, во что превратится твой код, когда обработка каждого конкретного типа будет занимать не 1-2 строки, а 5-10 или даже больше.
с визитором - это состояние пришлось явно передавать через конструктор
Очевидно, что в switch решениях она выше, чем в double dispatchне очевидно.
из того, что работает каждая операция по отдельности, совсем не следует, что работает и цельное решение.
Просто циклическая сложность функции с операцией.кстати ты подменяешь реальную цикломатическую сложность формальной("упрощенной").
Показатель цикломатической сложности является одним из наиболее распространенных показателей оценки сложности программных проектов. Данный показатель был разработан ученым Мак-Кейбом в 1976 г., относится к группе показателей оценки сложности потока управления программой и вычисляется на основе графа управляющей логики программы (control flow graph). Данный граф строится в виде ориентированного графа, в котором вычислительные операторы или выражения представляются в виде узлов, а передача управления между узлами – в виде дуг.
«Упрощенное» вычисление цикломатической сложности – предусматривает вычисление не на основе графа, а на основе подсчета управляющих операторов.http://cmcons.com/articles/CC_CQ/code_metrics_clearcase/
не очевидно.Ну если тебе это не очевидно, то тебе следует прочитать что-нибудь про unit testing. В своём посте я сравнивал сложность тестирования одной операции, а не каждой. В случае с Visitor мы тестируем отдельно взятые функции для каждого конкретного типа. В твоём случае для полного покрытия одной операции тест будет намного сложнее.
из того, что работает каждая операция по отдельности, совсем не следует, что работает и цельное решение.
Кроме того, как я уже написал выше, когда операции над конкретными типами будут сложнее, switch решение придётся разбить на вызовы отдельных функций. (А то и в самом деле получится спагетти.) Что как раз и будет полным аналогом double dispatch, которое кроме того может содержать ошибки в реализации.
кстати ты подменяешь реальную цикломатическую сложность формальной("упрощенной").Это сделано намеренно. В ассемблере даже multiple dispatch будет иметь высокую сложность. Но с точки зрения языка, в котором реализован multiple dipatch сложность будет равна 1. Да и покрытия unit test считаются не по инструкциям в ассемблере. Впрочем, мы отошли от темы.
Ну если тебе это не очевидно, то тебе следует прочитать что-нибудь про unit testing.имхо, скорее следует меньше переходить на личности, когда слов не хватает для формулирования своей позиции.
В своём посте я сравнивал сложность тестирования одной операции, а не каждой.на самом деле цикломатическая сложность у них одиннаковая и для тестирования обоих решений нужно одиннаковое кол-во тестов: по кол-ву погод.
на все случаи жизни возвращаемых типов не напасешьсяшаблон можно сделать.
Но с точки зрения языка, в котором реализован multiple dipatch сложность будет равна 1.пример такого решения можно, на котором будет наглядно показано, что вместо идеального кол-ва тестов m*n достаточно лишь m+n тестов.
Это сделано намеренно.ты забываешь, что для того, чтобы перейти от реальной цикломатической сложности к упрощенной - необходимо доказать, что поведение отдельных кусков кода независят друг от друга.
кстати именно этим плохи варианты с внешними переменными, как в Eval у , т.к. доказывать независимость становится очень сложно.
имхо, скорее следует меньше переходить на личности, когда слов не хватает для формулирования своей позиции.Почему же не хватает? Я просто не понимаю, как очевидная вещь может быть неочевидной. Что такое полное покрытие unit test-ом ты знаешь? Почему в твоём случае каждый Case имеет сложность равную 2 представляешь? Почему для полного покрытия такого проэмулированного switch-а надо написать более сложный unit test, чем при использовании встроенного в язык программирования switch понимаешь?
на самом деле цикломатическая сложность у них одиннаковая и для тестирования обоих решений нужно одиннаковое кол-во тестов: по кол-ву погод.Очень интересно, а как выглядит функция Case<T> ? И что будет, если какой-нибудь нерадивый разработчик её неправильно подправит под свои нужды? А что делать, если перед конкретизацией абстракции нужно её уточнение, или если это уточнение понадобится в будущем?
пример такого решения можно, на котором будет наглядно показано, что вместо идеального кол-ва тестов m*n достаточно лишь m+n тестов.Ты и сам можешь нагуглить реализацию Visitor на CLOS или F#/Ocaml.
Ты и сам можешь нагуглить реализацию Visitor на CLOS или F#/Ocaml.мне не нужна реализация, мне нужен показ того, что тестов нужно меньше.
ты забываешь, что для того, чтобы перейти от реальной цикломатической сложности к упрощенной - необходимо доказать, что поведение отдельных кусков кода независят друг от друга.Мы рассматриваем реализацию конкретизации абстракции, а не тела операций. Тела операций придётся тестировать в любом случае, просто твоё решение усложняет тестирование. В моём случае unit test для операции не ведёт к тестированию правильности реализации double dispatch, потому что она гарантирована компилятором и не может быть изменена неумелым программистом.
Что такое полное покрытие unit test-ом ты знаешь?так тебя опять же какое интересует?
полное или упрощенное?
полное - что пройдены все пути возможные в графе выполнения программы.
упрощенное - что хотя бы раз была выполнена каждая строка кода.
Почему в твоём случае каждый Case имеет сложность равную 2 представляешь?нет, не представляю.
особенно если учесть что эта некая библиотечная вещь.
Почему для полного покрытия такого проэмулированного switch-а надо написать более сложный unit test, чем при использовании встроенного в язык программирования switch понимаешь?не понимаю.
не понимаю, почему, по твоему мнению, на конструкцию (которая является эмуляцией встроенного foreach)
var result = items.Select(item => item.ToString.ToArray;
надо писать больше тестов, чем на конструкцию (которая реализована на встроенных в языке foreach-е)
var list = new List<string>
foreach (var item in items)
list.Add(item.ToString;
var result = list.ToArray;
как раз все наоборот, на вторую конструкцию надо писать больше тестов, чем на первую.
Очень интересно, а как выглядит функция Case<T> ?ровно также как выглядит код выбора нужной функции в визиторе
И что будет, если какой-нибудь нерадивый разработчик её неправильно подправит под свои нужды?сдохнут тесты библиотеки.
А что делать, если перед конкретизацией абстракции нужно её уточнение, или если это уточнение понадобится в будущем?для начала пример того, что надо
Это не пример грубейшей ошибки проектирования, а пример решения задачи. Иерархии в enum-е не будет, там будут перечислены все подтипы. Для расширяемости можно возвращать строку или guid, но опять же, с точки зрения проектирования это параллельно. Здесь незачем вдаваться в технические детали реализации, а то придётся дойти до момента, что фабрика классов, создающая конкретные типы, тоже должна быть расширяемой извне. (И продолжить тем, что в правильных языках это сделать в порядки проще, чем в C++/C#/Java.)Вот тут ты неясно зачем апеллировал к Visitor и заглушечным реализациям, которых я не предлагал. Т.е. ты добавил свои методы и тут же сравнил их с тобой же предложенными enum'ами.
В общем-то описанная проблема с enum будет возникать в том или ином виде и в Visitor. Ровно как и при полном дублировании всех интерфейсов с заглушечными реализациями - добавление нового конкретного класса может привести к требованию добавить новую функцию в самый главный интерфейс.
Значит так. Ты говоришь решения через getType и dynamic_cast эквивалентны. Вот смотри:
dynamic_cast: могу во внешнем коде добавить новый класс в иерархию, специализировать его, писать код, который обрабатывает его специфически.
getType: так не выйдет, какое значение возвращать из текущей функции? На базовый всем известный интерфейс? Надеюсь понимаешь, чем это плохо.
Тут уже говорили о COM. Так вот там нету никакого getType, там есть QueryInterface, по сути обобщенный dynamic_cast. Ты не задумывался, что это не просто так?
нет, не представляю.Какая разница, библиотечная вещь или нет? Её циклическая сложность равна двум - либо делегат выполнится, либо не выполнится. Очевидно, это усложняет как тестирование, так и анализ кода.
особенно если учесть что эта некая библиотечная вещь.
сдохнут тесты библиотеки.А что, если он её напишет так, что не сдохнут?
Мы рассматриваем реализацию конкретизации абстракции, а не тела операций.все равно получается в моем решение тестов меньше.
в моем решении надо один раз на всю жизнь протестировать правильность реализации конструкции
Switch(...)
.Case<TType1>(..)
.Case<TType2>(...)
в решении с визиторами, надо каждый раз тестировать эти самые визиторы:
WeatherVisitor для иерархии погод,
WheelVisitor для иерархии колес,
PeopleVisitor для иерархии людей
и т.д.
Какая разница, библиотечная вещь или нет? Её циклическая сложность равна двум - либо делегат выполнится, либо не выполнится. Очевидно, это усложняет как тестирование, так и анализ кода.ок. встречный вопрос - почему все это не применимо для WeatherVisitor-а?
сдохнут тесты библиотеки.
А что, если он её напишет так, что не сдохнут?
ps
и сколько ты кстати тестов предлагаешь для своего решения?
А что, если он её напишет так, что не сдохнут?извинится, когда все сдохнет.
как, например, было с ошибкой в pentium-е
или возможны какие-то другие варианты?
Вот тут ты неясно зачем апеллировал к Visitor и заглушечным реализациям, которых я не предлагал.Ты предложил паттерн State, который не решает поставленную задачу.
Ты говоришь решения через getType и dynamic_cast эквивалентныНет. Я говорю, что с точки зрения ООП они эквивалентны.
Ты приводишь примеры с точки зрения технической реализации, и никто не спорит, что их много всяких разных. Например, dynamic_cast может привести к родительскому или дочернему типу, что может быть нежелательно.
Нет. Я говорю, что с точки зрения ООП они эквивалентны.они совсем не эквивалентны.
это разбирается, и у Буча, и кратко у Страуструпа.
Ты предложил паттерн State, который не решает поставленную задачу.Вот перевирать, пожалуйста, попрошу не. На момент написания тобой цитируемого мной сообщения про State я еще не говорил. И это вообще отдельная история, которая тоже к проектированию имеет слабое отношение, один из _возможных_ способов реализации задачи без наследования на уровне самого класса погоды.
Нет. Я говорю, что с точки зрения ООП они эквивалентны.Хорошо, ты это утверждаешь, ты и докажи, я привел примеры, ты же аргументов не приводил, просто повторяешь одни и те же утверждения.
Ты приводишь примеры с точки зрения технической реализации, и никто не спорит, что их много всяких разных. Например, dynamic_cast может привести к родительскому или дочернему типу, что может быть нежелательно.Что может быть нежелательно? Функциональность dynamic_cast? К родительскому так и вообще он не нужен.
в моем решении надо один раз на всю жизнь протестировать правильность реализации конструкцииДа, я понял, что ты предлагаешь эмулировать visitor через switch конструкцию, добавив в код проблем и не уменьшив недостатков этого паттерна. Для каждой операции тебе придётся протестировать switch конструкцию (это помимо библиотеки) и каждую функцию, которая вызывается из этой switch конструкции. В случае с double dispatch есть только функции операции, каждую из которых надо протестировать отдельно. При росте числа операций, что и предполагается автором, тестов будет больше в случае со switch-ем.
в решении с визиторами, надо каждый раз тестировать эти самые визиторы:Нет, надо тестировать только методы этих классов. Double dispatch тебе обеспечивает компилятор. Ты можешь разве что аппелировать к правильности реализации Accept в иерархии, которая тестируется 1 раз для всех конкретных типов. Повторю, что switch конструкцию в твоём случае надо тестировать у каждой операции. В решении на языке с multiple dispatch будет ещё меньше тестов - там тестируются только функции операций.
WeatherVisitor для иерархии погод,
WheelVisitor для иерархии колес,
PeopleVisitor для иерархии людей
и т.д.
На момент написания тобой цитируемого мной сообщения про State я еще не говорил.Тогда извини, но я потерял ход твоих рассуждений.
Скажи ещё раз, как ты видишь применение паттерна State.
И повторись, в чём с точки зрения ООП (а не конкретного языка программирования) различаются серии dynamic_cast и switch конструкция.
Тогда извини, но я потерял ход твоих рассуждений.Еще раз скажу, что разговор не про State. Про него отдельно, открой для себя Threaded View и поймешь, может быть.
Скажи ещё раз, как ты видишь применение паттерна State.
И повторись, в чём с точки зрения ООП (а не конкретного языка программирования) различаются серии dynamic_cast и switch конструкция.Еще раз скажу, что это ты утверждаешь их эквивалентность, тебе и доказывать надо.
Вот, например, мое сообщение. Если выйдешь постепенно наверх, то увидишь, что тут разговор не про State, а про альтернативу getType/dyn_cast. И это ты то про Visitor вспоминаешь, то про State, в общем мечешься непонятно как-то, хотя предложил ты откровенную глупость с этим enum'ом.
Еще раз скажу, что это ты утверждаешь их эквивалентность, тебе и доказывать надо.А что там доказывать? ООП (кратко) - это подход в программировании, связанный с использованием классов и объектов и изучением связей между ними. Оба случая (и enum, и dynamic_cast) имеют одинаковое количество типов, но разное количество проблем из-за конкретного языка программирования. Выбери мы какой-нибудь другой язык программирования - и dynamic_cast нам не понадобится, и расширяемость будет высокой.
хотя предложил ты откровенную глупость с этим enum'омДа? А ты посчитай ту же сложность реализации операций с dynamic_cast и с enum и увидишь, что у обоих подходов есть как плюсы, так и минусы.
Нет, надо тестировать только методы этих классов. Double dispatch тебе обеспечивает компилятор.я правильно тебя понял, что ты считаешь, что, например, в решении не надо проверять, что функция Accept каждого класса Const, Add, Neg вызывает соответствующую функцию из класса Visitor?
и что это как-то мифически проверит компилятор...
Да? А ты посчитай ту же сложность реализации операций с dynamic_cast и с enum и увидишь, что у обоих подходов есть как плюсы, так и минусы.Дело не в сложности реализации, а в том, что enum - небезопасно, тяжело расширяется и несколько мешает инкапсуляции.
dyn_cast тоже в некотором роде противник инкапсуляции, но _гораздо_ в меньшей степени.
я правильно тебя понял, что ты считаешь, что, например, в решении не надо проверять, что функция Accept каждого класса Const, Add, Net вызывает соответствующую функцию из класса Visitor?Нет, внизу этого поста я приписал, что эти вызовы тестируются 1 раз для всех операций. Эмуляцию через switch конструкцию надо протестировать для каждой операции.
Кстати, твою switch конструкцию очень тяжело протестировать как unit. И ещё она повышает не только циклическую сложность, но и связность (в смысле coupling). В случае с Visitor мы можем понизить связность класса, например, передавая аргументы внутрь функций OnElement(float value)
Дело не в сложности реализации, а в том, что enum - небезопасно, тяжело расширяется и несколько мешает инкапсуляции.Я не могу увидеть твоих аргументов с точки зрения ООП. По-твоему без Си++ оно не существует? Чем enum небезопасен с точки зрения ООП? Как он мешает инкапсуляции?
dyn_cast тоже в некотором роде противник инкапсуляции, но _гораздо_ в меньшей степени.Я и не могу понять, почему?
Я не могу увидеть твоих аргументов с точки зрения ООП. По-твоему без Си++ оно не существует?Что «оно», извини?
Чем enum небезопасен с точки зрения ООП? Как он мешает инкапсуляции?Небезопасен и мешает инкапсуляции он потому, что раскрывает клиенту полностью информацию о динамическом типе. Опять же, почему в COM сделано QueryInterface, но нету чего-нибудь типа GetCLSID?
Я и не могу понять, почему?Противник потому что все равно заставляет программиста задумываться об иерархии классов, но в меньшей степени потому что согласованность гарантируется компилятором.
Что, оно, извини?ООП.
Небезопасен и мешает инкапсуляции он потому, что раскрывает клиенту полностью информацию о динамическом типе. Опять же, почему в COM сделано QueryInterface, но нету чего-нибудь типа GetCLSID?А какую мы решаем задачу? Пока что я считал, что мы конкретизиурем абстрактный класс. Сколько есть типов, которые надо конкретизировать, столько значений у enum, и столько же типов будет использоваться в большом if/else при использовании dynamic_cast.
В COM отделяют интерфейс от реализации, поэтому ничего общего с конкретизацией абстрактных классов там нет. COM-style решение уже описано в этой ветке. Кроме того, COM не основан на серии dynamic_cast. Поэтому мне слабо понятно, куда ты клонишь.
Эмуляцию через switch конструкцию надо протестировать для каждой операции.какое-нибудь обоснование все-таки будет для этого утверждения?
почему, например, недостаточно вот этого один раз в библиотеке
class Switch
{
interface ICase
{
bool Execute(object item);
}
class SwitchCase<T>:ICase
{
public SwitchCase(Action<T> action)
{
this.action = action;
}
Action<T> action;
public bool Execute(object item)
{
if (!(item is T
return false;
actionT)item);
return true;
}
}
public Switch Case<T>(Action<T> action)
{
cases.Add(new SwitchCase<T>(action;
return this;
}
List<ICase> cases = new List<ICase>
public void Execute(object item)
{
foreach (var @case in cases)
{
if (@case.Execute(item
break;
}
}
}
class Program
{
static void Main(string[] args)
{
string result = null;
var @switch =
new Switch
.Case<int>(i => {result = "int";})
.Case<Form>(i => { result = "Form"; })
.Case<Control>(i => { result = "Control"; })
.Case<object>(i => { result = "object"; })
;
var tests = new[]
{
new { Value = (object)1, Etalon = "int"},
new { Value = (object)new Form Etalon = "Form"},
new { Value = (object)new Control Etalon = "Control"},
new { Value = (object)new object Etalon = "object"},
};
foreach (var test in tests)
{
result = null;
@switch.Execute(test.Value);
if (result != test.Etalon)
Console.WriteLine("Тест для типа '{0}' не прошел. Ожидалось '{1}', а получили '{2}'",
test.Value.GetType.FullName, test.Etalon, result);
}
}
}
какое-нибудь обоснование все-таки будет для этого утверждения?Как без тестирования switch конструкции ты собираешься понять, правильные ли методы вызваны во всех случаях?
Твои тесты библиотеки проходят случай, когда я "для удобства" перепишу кусочек кода:
Уже потенциальная ошибка, которая может проявиться только в какой-то отдельно взятой switch конструкции. Поэтому несмотря на библиотеки эту конструкцию придётся тестировать полностью в каждой операции.
if (!typeof(T).IsAssignableFrom(item.GetType
return false;
Edit: Переписал код я неправильно. Мне стоило написать:
if (typeof(T) != item.GetType
return false;
Уже потенциальная ошибка, которая может проявиться только в какой-то отдельно взятой switch конструкции.пример для проявления данной ошибки, пожалуйста.
пример для проявления данной ошибки, пожалуйста.Очевидно, что операция может быть вызвана для типа, к которому её нельзя применять. Или не вызвана для типа, к которому её нужно было применить.
Очевидно, что операция может быть вызвана для типа, к которому её нельзя применять. Или не вызвана для типа, к которому её нужно было применить.чего? как это?
чего? как это?Просто, в иерархии эти типы могут состоять в отношении наследования.
Просто, в иерархии эти типы могут состоять в отношении наследования.это покроется тестами, что наш код правильно отрабатывает все варианты погоды.
А какую мы решаем задачу? Пока что я считал, что мы конкретизиурем абстрактный класс. Сколько есть типов, которые надо конкретизировать, столько значений у enum, и столько же типов будет использоваться в большом if/else при использовании dynamic_cast.Количество подтипов может изменяться в будущем, при этом очень легко забыть не расширить соответствующий enum, неужели непонятно?
Кроме того, COM не основан на серии dynamic_cast. Поэтому мне слабо понятно, куда ты клонишь.Как это не основан? Там QueryInterface — прямой аналог dynamic_cast.
И я не клоню, а утверждаю, что функциональность, которую ты хочешь _продублировать_ enum'ом уже есть в обсуждаемом языке программирования и твое решение _по этой причине_ отстой.
это покроется тестами, что наш код правильно отрабатывает все варианты погоды.То есть даже в том случае, когда тестировать какой-либо конкретный тип погоды не нужно, тебе всё равно придётся написать отдельный unit test на этот случай.
И ещё одна проблема возникнет, когда в одном случае потребуется одинаковый метод для разных типов, а в другом - разный код для этих же типов. Тогда ещё придётся следить за тем, что Case для базовых типов стоит перед Case для производных.
Я думаю, что привёл достаточно недостатков. И с этими недостатками я не увидел ни одного преимущества перед классической реализацией Visitor через double dispatch.
И я не клоню, а утверждаю, что функциональность, которую ты хочешь _продублировать_ enum'ом уже есть в обсуждаемом языке программирования и твое решение _по этой причине_ отстойФу, отвлекся. Не только по этой, а по той, что ты представляешь сразу конечный тип наружу. Это небезопасно.
То есть даже в том случае, когда тестировать какой-либо конкретный тип погоды не нужно, тебе всё равно придётся написать отдельный unit test на этот случай.разное поведение для разной погоды ожидаются или нет?
если ожидается, то в любом реализации - надо проверять все варианты погоды
если не ожидается, то и switch-я не будет.
И ещё одна проблема возникнет, когда в одном случае потребуется одинаковый метод для разных типов, а в другом - разный код для этих же типов.а в визиторе это как-то магически решится?
тебе все равно придется писать два разных визитора, и оба раза их независимо тестировать.
Тогда ещё придётся следить за тем, что Case для базовых типов стоит перед Case для производных.
это может и сам switch проверить
Я думаю, что привёл достаточно недостатков. И с этими недостатками я не увидел ни одного преимущества перед классической реализацией Visitor через double dispatch.наглядность и атомарность выше
double dispatch - только скорость обеспечивает, но никак не уменьшение тестов, или улучшение читабельности.
все перешли к обсуждению частных проблем ООП?
---
Моё знакомство с ООП началось со слова "интифада."
Я правильно понимаю, что от исходного вопроса "как моделировать,"Правильно. А ты запрещаешь? Можешь отвечать на первое сообщение.
все перешли к обсуждению частных проблем ООП?
Количество подтипов может изменяться в будущем, при этом очень легко забыть не расширить соответствующий enum, неужели непонятно?Для этого сначала надо "легко забыть" не расширить не одну операцию для этого нового типа.
Как это не основан? Там QueryInterface — прямой аналог dynamic_cast.Просто, QueryInterface в COM не основан на серии dynamic_cast. Он сам по себе является семантическим аналогом dynamic_cast, и вовсе не имеет никакого отношения к решаемой нами задаче. Кроме того, он основан на решении, подобном enum-ам. И если уж ты так беспокоишься о безопасности и расширяемости, то именно для того, чтобы повысить безопасность и расширяемость в COM перешли от dynamic_cast к решению, основанному на GUID-ах.
И я не клоню, а утверждаю, что функциональность, которую ты хочешь _продублировать_ enum'ом уже есть в обсуждаемом языке программирования и твое решение _по этой причине_ отстой.Не отстой, а имеет какие-то свои минусы. И этих минусов, кстати, даже меньше, чем в решении с dynamic_cast. И вот тут уже можно было бы приводить QueryInterface как аргумент.
И этих минусов, кстати, даже меньше, чем в решении с dynamic_castТак ты ни одного еще не привел минуса dynamic_cast
>> все перешли к обсуждению частных проблем ООП?
> Правильно.
Прекрасно, больше вопросов (пока?) не имею.
> А ты запрещаешь?
Зачем? Клёво же!
---
Моё знакомство с ООП началось со слова "интифада."
Для этого сначала надо "легко забыть" не расширить не одну операцию для этого нового типа.Ну вот неужели непонятно? Будут у нас два клиента этой библиотеки, один добавит один производный класс, другой — другой. Оба назначат один и тот же идентификатор, но унаследуют, скажем, от разных базовых классов. «Bang, you're dead!»
наглядность и атомарность вышеПочему это наглядность выше, если увеличилась циклическая сложность и добавилась какая-то дополнительная generic библиотека со сложным поведением?
double dispatch - только скорость обеспечивает, но никак не уменьшение тестов, или улучшение читабельности.
Почему выше атомарность, если увеличена связность (coupling) и происходит дублирование кода?
Так ты ни одного еще не привел минуса dynamic_castТак ты сам тут писал про COM и QueryInterface. Ты перед этим не прочитал, зачем это было сделано? Как раз чтобы исправить минусы dynamic_cast.
именно для того, чтобы повысить безопасность и расширяемость в COM перешли от dynamic_cast к решению, основанному на GUID-ах.Компоненты и клиенты COM можно писать практически на любом языке программирования (скажем, на Cи точно можно). Поэтому утверждение, что они там перешли от чего-то к dynamic_cast абсурдно.
Почему это наглядность выше, если увеличилась циклическая сложность и добавилась какая-то дополнительная generic библиотека со сложным поведением?обоснуй, пожалуйста. все три утверждения не наблюдаю.
Почему выше атомарность, если увеличена связность (coupling) и происходит дублирование кода?
Так ты сам тут писал про COM и QueryInterface. Ты перед этим не прочитал, зачем это было сделано? Как раз чтобы исправить минусы dynamic_cast.Это ты только что придумал, это неправда =) Про COM я писал как про аналогию, QueryInterface работает сходным с dynamic_cast образом, не с getType.
исправить минусы dynamic_cast.Истинные утверждения и по делу писать будешь или закончим? Я от тебя прошу: список минусов dynamic_cast по отношению к getType в контексте вопроса топикстартера. Надеюсь несложно изложил?
Я от тебя прошу: список минусов dynamic_cast по отношению к getType в контексте вопроса топикстартера.Два разных компилятора.
Более высокий class coupling отдельных функций.
Более высокая алгоритмическая сложность (код будет намного медленнее работать).
Придётся писать dynamic_cast для потомков перед dynamic_cast для родителей, а это дополнительные ошибки в программе.
Поэтому утверждение, что они там перешли от чего-то к dynamic_cast абсурдно.Именно. Потому что они перешли от dynamic_cast к чему-то, посты читай внимательно.
Два разных компилятора.Ха. Так static_cast тоже могут два разных компилятора навернуть. Я тебе даже больше скажу: может вообще механизм виртуальных функций не сработать =)
Более высокий class coupling.Не вижу почему с getType он «более низкий»
Более высокая алгоритмическая сложность (код будет намного медленнее работать).Допустим.
Придётся писать dynamic_cast для потомков перед dynamic_cast для родителей, а это дополнительные ошибки в программе.Легко отлавливается =)
О чем вы?Я о двух разных компиляторах.
Не вижу почему с getType он «более низкий»Потому что можно выкидывать исключения или совершать действия, основываясь на значении enum-а.
Потому что можно выкидывать исключения или совершать действия, основываясь на значении enum-а.Ну так когда ты связываешься с определенным типов, как раз у тебя этот самый coupling и увеличивается!
обоснуй, пожалуйста. все три утверждения не наблюдаю.Ты всегда отвечаешь вопросом на вопрос?
Почему это наглядность выше, если увеличилась циклическая сложность и добавилась какая-то дополнительная generic библиотека со сложным поведением?Что из этого ты не наблюдаешь? Циклическую сложность классов и функций тебе распишет любой автоматический анализатор кода. Или ты не наблюдаешь, что добавилась какая-то библиотека со своими правилами работы? В которой сортируются типы по отношению наследования и проверяются, по очереди вызывая делегаты? Или ты не замечаешь, что полностью продублировал фукнциональность языка? Сделал какой-то странный "ручной" полиморфизм.
Почему выше атомарность, если увеличена связность (coupling) и происходит дублирование кода?Этого ты тоже не наблюдаешь? Посчитать связность классов и функций могут автоматические анализаторы кода. Дублирование кода будет в случае, когда будет замена твоим способом вызова Accept, который что-то делает с данными конкретного класса. Связность нарастает из-за того, что ты не можешь отказаться от типов в твоей switch конструкции. В то время как Visitor может быть независимым от конкретных типов, выполняя операции с их полями.
Ну так когда ты связываешься с определенным типов, как раз у тебя этот самый coupling и увеличивается!С каким типом я связываюсь?
if (dynamic_cast<C1*>(pc
independant_of_c1_method;
else if (dynamic_cast<C2*>(pc
independant_of_c2_method;
else if (dynamic_cast<C3*>(pc
independant_of_c3_method;
switch (enum) + 3 case/break + все три метода.
С каким типом я связываюсь?Ну вот, я добавляю еще один тип в иерархию. Первый метод работает корректно: с доступным интерфейсом (если скастилось, то хорошо). Твой же код не ясно как будет работать.
if (dynamic_cast<C1*>(pc
independant_of_c1_method;
else if (dynamic_cast<C2*>(pc
independant_of_c2_method;
else if (dynamic_cast<C3*>(pc
independant_of_c3_method;
switch (enum) + 3 case/break + все три метода.
Ну вот, я добавляю еще один тип в иерархию. Первый метод работает корректно: с доступным интерфейсом (если скастилось, то хорошо). Твой же код не ясно как будет работать.Точно так же!
Точно так же!Ага, т.е. ты предлагаешь у вновь созданного класса в качестве getType возвращать тот же результат, что и у предка? Или как иначе это можно реализовать?
Ага, т.е. ты предлагаешь у вновь созданного класса в качестве getType возвращать тот же результат, что и у предка? Или как иначе это можно реализовать?Нет, я предлагаю возвращать новый.
Я считаю, что мы уже слишком далеко отошли от темы. Ещё раз повторюсь, моё утверждение было в том, что dynamic_cast и enum одинаковы с точки зрения ООП. Я не спорю, что они отличаются с точки зрения конкретного языка программирования Си++ Я не спорю, что у каждого из этих методов есть недостатки, причём этих недостатков достаточно много в обоих случаях. Считаю, что дальнейшая дискуссия языка Си++ и его проблем нецелесообразна.
Нет, я предлагаю возвращать новый.А как тогда твой switch без изменений отработает? Там тогда будет попадание в ветку default и что? Через самый базовый класс полезет.
Ещё раз повторюсь, моё утверждение было в том, что dynamic_cast и enum одинаковы с точки зрения ООП.Да, так оно было неверным, я это тебе пытаюсь показать, указал на книги, почему ты не согласен — не знаю.
Ещё раз повторюсь, моё утверждение было в том, что dynamic_cast и enum одинаковы с точки зрения ООП.если они эквивалентны, то как через enum реализуется следующий код?
interface IA{}
interface IB:IA
{
int X {get;}
}
interface IC:IA
{
int Z {get;}
}
void Main
{
IA a = GetCurrentA;
if (a is IB)
Console.WriteLineIB)a).X);
if (a is IС)
Console.WriteLineIС)a).Z);
}
А как тогда твой switch без изменений отработает? Там тогда будет попадание в ветку default и что? Через самый базовый класс полезет.Когда оно попадёт в default то сделает тоже самое, что и серия твоих dynamic_cast в последнем else.
если они эквивалентны, то как через enum реализуется следующий код?Во-первых, речь шла о Си++, на C# достаточно typeof. Во-вторых, такое решение стоит применять только в том случае, если операция, требующая конкретизации одна. В остальных случаях ты только навредишь таким подходом. В третьих, в Си++ нету чистых интерфейсов, там будут абстрактные классы и поставленную тобой задачу я решу очень быстро - просто реализовав функцию get_type во всех абстрактных классах. С точки зрения ООП это продолжает оставаться эквивалентом dynamic_cast. В четвёртых, стоило приводить примеры с множественным наследованием, геморой будет разве что там.
QueryInterface в COM не основан на серии dynamic_cast. Он сам по себе является семантическим аналогом dynamic_castНе является он _семантическим_ аналогом dynamic_cast, потому что может вообще создавать новые объекты, делать много других ужасных вещей. Ну что ты несешь?
Да, так оно было неверным, я это тебе пытаюсь показать, указал на книги, почему ты не согласен — не знаю.Потому что ООП не ограничивается языком Си++, а для меня технические проблемы того или иного языка не являются проблемами ООП.
Когда оно попадёт в default то сделает тоже самое, что и серия твоих dynamic_cast в последнем else.Выходит ты не знаешь как работает dynamic_cast. Давай проверим.
struct A {};
struct B : A {};
struct C : B {};
...
A *p = new C;
dynamic_cast< B* >( p ) == ?
Потому что ООП не ограничивается языком Си++, а для меня технические проблемы того или иного языка не являются проблемами ООП.Так ты не видишь главного. Мы не о языке, а о том, что вместо метода getType надо предоставить только методы типа QueryInterface.
Вот, можешь почитать например это
http://www.objectmentor.com/resources/articles/lsp.pdf
не обязательно целиком, там про сравнение типов ближе к началу написано.
Не является он _семантическим_ аналогом dynamic_cast, потому что может вообще создавать новые объекты, делать много других ужасных вещей. Ну что ты несешь?Я всего лишь процитировал книгу Дональда Бокса "Сущность технологии COM"
Приведи пример QueryInterface, который создаёт новые объекты.
Тему COM технологий хотелось бы вынести в отдельную ветку, она мало связана с темой автора.
Тему COM технологий хотелось бы вынести в отдельную ветку, она мало связана с темой автора.Да нету тут никакой темы COM-технологий, тут аналогия, знакомо тебе такое понятие?
Приведи пример QueryInterface, который создаёт новые объекты.Какого рода тебе пример нужен? Я сейчас спокойно напишу компонент, у которого есть довольно редко используемый интерфейс, при запросе которого я буду создавать соответствующий объект, его реализующий и возвращать, запоминая ссылку. Подсчет ссылок тоже не зря ведут отдельно для интерфейсов, когда он обнулится, я этот объект удалю. И все в шоколаде.
Выходит ты не знаешь как работает dynamic_cast. Давай проверим.Нет, не выходит. Давай проверим:
if (dynamic_cast<C1*>(pc) != null)
do_1;
else if (dynamic_cast<C2*>(pc) != null)
do_2;
else if (dynamic_cast<C3*>(pc) != null)
do_3;
else
do_default;
Какого рода тебе пример нужен? Я сейчас спокойно напишу компонент, у которого есть довольно редко используемый интерфейс, при запросе которого я буду создавать соответствующий объект, его реализующий и возвращать, запоминая ссылку. Подсчет ссылок тоже не зря ведут отдельно для интерфейсов, когда он обнулится, я этот объект удалю. И все в шоколаде.Пример самой простой QueryInterface которая создаёт объекты.
Ну вот если я тут унаследую от C2, то будет вызван вариант, где кастуют к C2, а не default, предложи-ка реализацию с enum'ами такого поведения.
Пример самой простой QueryInterface которая создаёт объекты.ты до конца дочитал сообщение?
Да нету тут никакой темы COM-технологий, тут аналогия, знакомо тебе такое понятие?Я не вижу аналогии между эмуляцией полиморфизма через QueryInterface и задачей конкретизациии абстрактных классов.
Ну вот если я тут унаследую от C2, то будет вызван вариант, где кастуют к C2, а не default, предложи-ка реализацию с enum'ами такого поведения.Вернуть соответствующее enum значение.
ты до конца дочитал сообщение?Я дочитал до конца сообщение, но меня интересует простейший код с реализацией. Самый простой, который только можно.
Пример самой простой QueryInterface которая создаёт объекты.
Вопросы? Или я это зря написал?
interface IBase : IUnknown
{
...
};
interface IExtended : IUnknown
{
...
};
class Component : IBase
{
HRESULT QueryInterface(...)
{
if (iid == IID_EXTENDED)
{
if (pExt == 0)
{
pExt = new ExtPart(...);
}
*ppv = pExt;
} else ...
(reinterpret_cast< IUnknown * >( *ppv ->AddRef;
return S_OK;
}
...
class ExtPart : IExtended
{
...
};
private:
IExtended *pExt;
};
Вернуть соответствующее enum значение.Ну а как тогда я отличу C2 от его наследника? А никак.
Ну а как тогда я отличу C2 от его наследника? А никак.Интересно, ты прикалываешься или серьёзно? Точно так же, как ты стал бы отличать их в случае dynamic_cast. В твоём случае добавился бы один dynamic_cast, в моём случае дописывается значение enum.
Интересно, ты прикалываешься или серьёзно? Точно так же, как ты стал бы отличать их в случае dynamic_cast. В твоём случае добавился бы один dynamic_cast, в моём случае дописывается значение enum.А ты? Напиши реализацию функции getType в C2 и в наследнике, а потом покажи как твой switch зайдет в вариант для C2, если там наследник на самом деле.
В этом коде не видно очень важной части. Я имею полное право сказать, что он написан с ошибкой.
Я не вижу аналогии между эмуляцией полиморфизма через QueryInterface и задачей конкретизациии абстрактных классов.Не там ты ее ищешь потому что. Аналогия между QueryInterface и dynamic_cast в том, что было сделано предпочтение QueryInterface вместо возврата кода компонента, что привязывало бы клиента к реализации. Вот только не говори, что «задача конкретизации абстрактных классов» не должна отделять интерфейс от реализации, ибо это основа ООП.
А ты? Напиши реализацию функции getType в C2 и в наследнике, а потом покажи как твой switch зайдет в вариант для C2, если там наследник на самом деле.Очень просто, get_type вернёт разные значения enum-а для базового класса и для наследника.
Я уже написал, что этот подход имеет свои плюсы и минусы. Никак не связанные с ООП.
Во-первых, речь шла о Си++, на C# достаточно typeof. Во-вторых, такое решение стоит применять только в том случае, если операция, требующая конкретизации одна. В остальных случаях ты только навредишь таким подходом. В третьих, в Си++ нету чистых интерфейсов, там будут абстрактные классы и поставленную тобой задачу я решу очень быстро - просто реализовав функцию get_type во всех абстрактных классах. С точки зрения ООП это продолжает оставаться эквивалентом dynamic_cast. В четвёртых, стоило приводить примеры с множественным наследованием, геморой будет разве что там.так код будет или нет?
слив засчитан.
В этом коде не видно очень важной части. Я имею полное право сказать, что он написан с ошибкой.Да, там многоточия, они не компиляются, поясняй.
Очень просто, get_type вернёт разные значения enum-а для базового класса и для наследника.КАК ТОГДА твой switch узнает, что код для наследника C2 и код самого C2 как-то связаны? То-то, никак, об этом я тебе и толкую!
КАК ТОГДА твой switch узнает, что код для наследника C2 и код самого C2 как-то связаны? То-то, никак, об этом я тебе и толкую!Чего? Лучше приведи пример кода с dynamic_cast, который я перепишу на enum-ы. (Поскольку я выше по теме уже подсказал про множественное наследование, я ожидаю, что приведённый код не будет его содержать.)
Чего? Лучше приведи пример кода с dynamic_cast, который я перепишу на enum-ы. (Поскольку я выше по теме уже подсказал про множественное наследование, я ожидаю, что приведённый код не будет его содержать.)Окей. Только без сливов на этот раз, хорошо?
struct A
{
};
struct B : A
{
};
void f(A *p)
{
if (dynamic_cast< B* >( p ) != 0)
specific_to_B( p );
else
default_to_A( p );
}
Далее, добавляется класс C:
struct C : B {};
В моем случае будет правомерно вызвана specific_to_B.
Пейши аналог с enum'ами.
Ну так что, где там _ошибка_ в моем наивном QueryInterface? Причем, где доказательство, что я не могу создавать объекты в QE? Да ты и не найдешь этого, т.к. так все и делают =)
так код будет или нет?Какой код? Этот? Простое преобразование interface to abstract class, его даже можно сделать автоматически. В Си++ и того проще, стереть "= 0" и добавить тело функции.
abstract class A1
{
public virtual TheEnum GetEnum
{
return TheEnum.E1;
}
}
слив засчитан.Какой слив? Слив пока что защитан только тебе - ты проэмулировал полиморфизм более сложным программным методом.
Ну так что, где там _ошибка_ в моем наивном QueryInterface? Причем, где доказательство, что я не могу создавать объекты в QE? Да ты и не найдешь этого, т.к. так все и делают =)В твоём коде нет очень важной части - функции QI у Extended класса.
В Си++ и того проще, стереть "= 0" и добавить тело функции.эквивалентный код где?
// CppTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
struct IA
{
virtual ~IA{}
};
struct IB:IA
{
virtual int X = 0;
};
struct IC:IB
{
virtual int Z = 0;
};
struct C:IC
{
int X{return 5;}
int Z{return 15;}
};
struct B:IB
{
int X{return 5;}
};
struct A:IA
{
};
IA* GetA(int i)
{
switch (i)
{
case 3:
return new C;
case 2:
return new B;
default:
return new A;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int i;
std::cin >> i;
IA* a= GetA(i);
IB* b = dynamic_cast<IB*>(a);
if (b != NULL)
std::cout << b->X << std::endl;
IC* c = dynamic_cast<IC*>(a);
if (c != NULL)
std::cout << c->Z << std::endl;
return 0;
}
Пейши аналог с enum'ами.enum EType
{
type_a,
type_b,
type_c
};
void f(A *p)
{
switch(p->get_type
{
default:
default_to_A( p );
break;
case type_b:
case type_c:
specific_to_B(static_cast<B*>(p;
break;
}
}
ты проэмулировал полиморфизм более сложным программным методом.вообще это ты тут эмулируешь полиморфизм enum'ами, нет?
вообще это ты тут эмулируешь полиморфизм enum'ами, нет?С некоторой стороны - именно это я и делаю. И выше по теме я уже рассуждал о различных проблемах, связанных с языком Си++, dynamic_cast и enum.
// DelCpp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
enum the_type
{
type_a,
type_b,
type_c
};
struct IA
{
virtual ~IA { }
virtual the_type get_type
{
return type_a;
}
};
struct IB : IA
{
virtual int X = 0;
virtual the_type get_type
{
return type_b;
}
};
struct IC : IB
{
virtual int Z = 0;
virtual the_type get_type
{
return type_c;
}
};
struct C : IC
{
int X { return 5; }
int Z { return 15; }
};
struct B : IB
{
int X { return 5; }
};
struct A : IA
{
};
IA* GetA(int i)
{
switch (i)
{
case 3:
return new C;
case 2:
return new B;
default:
return new A;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int i;
std::cin >> i;
IA* a = GetA(i);
the_type type = a->get_type;
if (type == type_b || type == type_c)
std::cout << IB*)a)->X << std::endl;
if (type == type_c)
std::cout << IC*)a)->Z << std::endl;
return 0;
}
enum ETypeНет-нет. Ты дурачком-то не прикидывайся =)
{
type_a,
type_b,
type_c
};
void f(A *p)
{
switch(p->get_type
{
default:
default_to_A( p );
break;
case type_b:
case type_c:
specific_to_B(static_cast<B*>(p;
break;
}
}
Ты напиши как у меня. Кто за тебя пройдется по коду и поправит все эти enum'ы?
Вердикт:
Не, ну ты реальне лошок, прочитал пару книжек, а вынес из них не очень-то много.
Нет-нет. Ты дурачком-то не прикидывайся =)Тот же, кто пройдётся по коду у тебя и поправит dynamic_cast-ы в случае, когда для B и C надо совершать разные действия.
Ты напиши как у меня. Кто за тебя пройдется по коду и поправит все эти enum'ы?
Вердикт:Я хотя бы прочитал эти книжки. И не делаю глупостей, рассуждая о технологиях 95-го года. Хотя как оказалось, память у меня нормальная. По крайней мере развёл тебя на ошибку в коде QI. Так что предлагаю тебе самому почитать книжку про COM, обычно теме QI целая глава отводится. После этого можешь посмотреть в зеркало и повторить свой вердикт.
Не, ну ты реальне лошок, прочитал пару книжек, а вынес из них не очень-то много.
и ты серьезно считаешь эти решения эквивалентными с точки зрения ООП?
и после этого с умным видом рассуждаешь об увеличиение coupling-а?
в первом решении, как минимум была инкапсуляция (один из китов ООП).
функция main могла не знать какие бывают классы реализации интерфейсов IA, IB, IC, и соответственно не менялась при добавление классов D, E, F и т.д.
соответственно, и цикломатическая сложность функции main, и coupling функции main были минимальны,
в твоем же решении - и то, и другое резко подскакивает.
в твоем же решении - и то, и другое резко подскакивает.Выше по теме я уже отписался, что в обоих подходах есть плюсы и минусы. И рассмотрел примеры минусов обоих подходов. И да, я продолжаю считать, что с точки зрения ООП решения эквивалентны.
функция main могла не знать какие бывают классы реализации интерфейсов IA, IB, IC, и соответственно не менялась при добавление классов D, E, F и т.д.
А что, у меня меняется? Ну разве что я промахнулся по клавиатуре, с кем не бывает.
соответственно, и цикломатическая сложность функции main, и coupling функции main были минимальныТак можо привести примеры, когда циклическая или алгоритмическая сложность вырастает в одном случае и падает в другом; и наоборот. Я же не спорю, что в обоих подходах есть свои минусы.
И да, я продолжаю считать, что с точки зрения ООП решения эквивалентны.слово "эквивалентность" подразумевает, что одно можно преобразовать в другое и обратно
вот такой кусок кода ты не можешь преобразовать в аналогичный, но с enum-ами, не имея перед глазами код всех будущих программ мира
struct IA
{
virtual ~IA{}
};
struct IB:IA
{
virtual int X = 0;
};
struct IC:IB
{
virtual int Z = 0;
};
int _tmain(int argc, _TCHAR* argv[])
{
int i;
std::cin >> i;
IA* a= GetA(i);
IB* b = dynamic_cast<IB*>(a);
if (b != NULL)
std::cout << b->X << std::endl;
IC* c = dynamic_cast<IC*>(a);
if (c != NULL)
std::cout << c->Z << std::endl;
return 0;
}
А что, у меня меняется? Ну разве что я промахнулся по клавиатуре, с кем не бывает.при добавлении вот такого класса D функцию main надо переписывать
struct ID:IC
{
virtual int Q = 0;
};
struct D:ID
{
int X{return 5;}
int Z{return 15;}
int Q{return 10;}
};
По крайней мере развёл тебя на ошибку в коде QI.Ну-ка ну-ка, ты пока так и не сказал, что имеешь в виду.
Ну-ка ну-ка, ты пока так и не сказал, что имеешь в виду.Я сказал - ты не привёл код QI у реализации IExtended. В этом случае это было критично и является ошибкой. Я тебе предлагаю самостоятельно ознакомиться с темой и написать тот же код со вторым методом QI у реализации IExtended.
Если ты требуешь, чтобы я указал пальцем на место ошибки, то я могу это сделать. Но всё же хочу, чтобы до решения дошёл ты сам. Может, перестанешь так сразу обзывать людей.
при добавлении вот такого класса D функцию main надо переписыватьПочему? Вот я не переписал, она работает:
IA* GetA(int i)
{
switch (i)
{
case 4:
return new D;
case 3:
return new C;
case 2:
return new B;
default:
return new A;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int i;
std::cin >> i;
IA* a = GetA(i);
the_type type = a->get_type;
if (type == type_b || type == type_c)
std::cout << IB*)a)->X << std::endl;
if (type == type_c)
std::cout << IC*)a)->Z << std::endl;
return 0;
}
Почему? Вот я не переписал, она работает:отлично.
теперь добавляем функцию
void another_main
{
IA* a= GetA(i);
IB* b = dynamic_cast<IB*>(a);
if (b != NULL)
std::cout << b->X << std::endl;
IC* c = dynamic_cast<IC*>(a);
if (c != NULL)
std::cout << c->Z << std::endl;
ID* d = dynamic_cast<ID*>(a);
if (d != NULL)
std::cout << d->Q << std::endl;
}
какой будет эквивалентный код?
какой будет эквивалентный код?Какой будет код ты знаешь. Но теперь нам обоим пришлось переписать функцию.
этом случае это было критично и является ошибкой.ошибки еще нет, т.к. не указана реализация IUnknown-а у ExtPart
Какой будет код ты знаешь. Но теперь нам обоим пришлось переписать функцию.напомню, что функцию main я не трогал, я добавил другую функцию
а тебе придется main потрогать - это на тему цикломатической сложности и coupling-а
ошибки еще нет, т.к. не указана реализация IUnknown-а у ExtPartЯ не согласен. Не привести здесь эту реализацию уже является ошибкой. Особенно после таких авторитетных заявлений о том, что я лошок.
Я сказал - ты не привёл код QI у реализации IExtended. В этом случае это было критично и является ошибкой. Я тебе предлагаю ознакомиться с темой и написать тот же код со вторым методом QI у реализации IExtended.Я-то думал ты хоть что-нибудь по делу предъявишь. Ты просил реализацию QI, в которой создаются объект, я тебе ее предоставил, теперь ты хочешь, чтобы я тебе всю программу предъявил? На каком основании? Я свою точку зрения отстоял, ты же демонстрируешь свою полнейшую некомпетентность и наличие какой-то кучи несвязанной воедино информации.
Я не согласен. Не привести здесь эту реализацию уже является ошибкой. Особенно после таких авторитетных заявлений о том, что я лошок.У тебя что-то точно не в порядке с определением последовательности действий. Я _сначала_ написал код, и уже _после_ назвал тебя лошком.
напомню, что функцию main я не трогал.Да, в этом случае придётся. Но есть много способов переписать и так, чтобы не пришлось: вернуть список, или вынести операцию из функции, положившись на уровни иерархии. Повторюсь, что я не спорю о том, что все эти методы имеют плюсы и минусы.
а тебе придется ее потрогать.
У тебя что-то точно не в порядке с определением последовательности действий. Я _сначала_ написал код, и уже _после_ назвал тебя лошком.Код, который ты написал, является неверным. Или, если принять позицию , то неполным. Но эта неполнота кода является критичной. Если ты такой знаток, то должен был это понять и привести полный и правильный код.
Вопрос состоит в том, напишешь полный код ты сам или это придётся сделать мне? Для его полноты нужна только функция QI в реализации IExtended.
class ExtPart : IExtended
{
private:
Component *cmpnt;
public:
ExtPart(Component *_cmpnt) : cmpnt( _cmpnt ) {}
HRESULT QueryInterface(...)
{
if (id == IID_EXTENDED)
{
*ppv = static_cast<IExtended*>( this );
} else if (id == IID_UNKNOWN) // тут честно не помню как называется константа GUID для IUnknown
{
*ppv = static_cast<IUnknown *>( cmpnt );
} else if (id == IID_BASE)
{
*ppv = static_cast<IBase *>( cmpnt );
} else ...
(reinterpret_cast<IUnknown *>( *ppv ->AddRef;
return S_OK;
}
void Release
{
if (--refcnt == 0)
{
cmpnt->pExt = 0;
delete this;
}
}
};
вот как-то так.
Ладно, я сегодня добрый.Ну ещё было бы неплохо быть честным и признать свою ошибку. Впрочем, я на это не надеюсь.
Хоть это и оффтоп, но вот тебе правильная реализация QI у ExtPart'а:Она более правильная, чем её отсутствие. Но только теперь этот код будет либо падать, либо не сможет освобождать память. Метод Release и конструктор ты приписал совсем зря, потому что они тоже неправильные.
Вообще то, чем ты пытаешься тут заниматься, в технологии COM называется композицией и является одним из сложных механизмов этой технологии.
Она более правильная, чем её отсутствие. Но только теперь этот код будет либо падать, либо не сможет освобождать память. Метод Release и конструктор ты приписал совсем зря, потому что они тоже неправильные.Это набросок реализации. И про композицию тут все знают, ты что, думаешь глаза тут кому-то открыл?
Вообще то, чем ты пытаешься тут заниматься, в технологии COM называется композицией и является одним из сложных механизмов этой технологии.
Ты давай по делу, а то это неправильно, то неправильно, а конкретно указать не можешь.
Ну ещё было бы неплохо быть честным и признать свою ошибку. Впрочем, я на это не надеюсь.Какую ошибку? То, что ты видешь неточности в ненаписанном коде? Или что ты не понимаешь, что не надо возвращать никаких enum'ов?
Это набросок реализации. И про композицию тут все знают, ты что, думаешь глаза тут кому-то открыл?Хорошо. Это набросок реализации с существенными ошибками с точки зрения COM и языка Си++. После таких набросков обычно не пишут слова типа "слив" и "лошок".
Ты давай по делу, а то это неправильно, то неправильно, а конкретно указать не можешь.Я сразу же указал конкретно и по делу. Может быть, я зря дал шанс тебе изучить тему и оправдаться.
Это набросок реализации с существенными ошибками с точки зрения COM и языка Си++.укажи на них, если ты знаешь. Иначе это выглядит как оправдание.
После таких набросков обычно не пишут слова типа "слив" и "лошок".И все же, в каком порядке это произошло? =)
Я сразу же указал конкретно и по делу. Может быть, я зря дал шанс тебе изучить тему и оправдаться.Ты сказал, что ошибка кроется в участке, который я не привел. Это конкретно? По делу?
Какую ошибку? То, что ты видешь неточности в ненаписанном коде?Я тебя попросил реализацию QI, где ты создавал бы новый объект. Ты забыл написать там несколько важных вещей. Ты так глубоко разбираешься в теме, что не способен написать динамическую композицию COM объектов без визарда в Visual Studio? Мог бы хотя бы скопировать её оттуда для разнообразия. Или дать мне ссылку на MSDN или конкретный исходник MFC.
Странно, что после часа времени, которое у тебя было, чтобы разобраться в теме, ты выдал код, который неправильно работает. В нём во-первых, течёт память или возникает обращение к уже освобождённой памяти. А во-вторых, QueryInterface не делегирует своему родителю.
После того, как я сказал, что код содержит ошибку или является неполным, любой, кто ещё помнит 95-98 годы и COM мог бы сразу расставить точки над и. Но в этой теме тебе указали на ошибку несколько раз, прежде чем ты написал-таки код. Правда, опять с ошибками.
в технологии COM называется композициейВо-первых, ты хотел сказать аггрегирование? Если да, то ты не прав. Аггрегирование - это метод _повторного использования_ компонента. Тут же я просто необычным методом предоставляю интерфейс. Для этого я даже запихнул определение класса ExtPart внутрь основного класса компонента, это деталь реализации.
Ты забыл написать там несколько важных вещей.Кроме делегирования вызова _ничего_ пока не заметил. Из визардов ничего копировать не собираюсь.
То что должно быть делегирование - это не аксиома, тут явно оба класса подконтрольны одному человеку, один внутри другого находится, реализую я на свое усмотрение.
Приведи пример кода, когда течет память! Слабо?
существенными ошибками с точки зрения COM и языка Си++Про язык Си++ хотя бы ответь: тут проще проконтроллировать, можешь ли ты отвечать за свои слова. Если не ответишь, дальше продолжать разговор не собираюсь.
Тут же я просто необычным методом предоставляю интерфейс. Для этого я даже запихнул определение класса ExtPart внутрь основного класса компонента, это деталь реализации.если в терминах atl, то это tear-off interfaces
Во-первых, ты хотел сказать аггрегирование?Нет, я хотел сказать "динамическая композиция" или "отделяемый интерфейс". Это именно то, что ты сделал.
С точки зрения COM ты не делегировал вызовы QI и неправильно посчитал ссылки.
С точки зрения Си++ у тебя либо течёт память, либо вызывается метод уже освобождённого объекта. Это можно понять в зависимости от того, что вставлено в new ExtPart(...); Если там написано слово this, то в общем коде происходит обращение к освобождённой памяти.
Это можно понять в зависимости от того, что вставлено в new ExtPart(...); Если там написано слово this, то в общем коде происходит обращение к освобождённой памяти.Вот опять чем ты занимаешься? Домысливаешь за меня участки кода с ошибками? Думаешь я нормально не реализую подсчет ссылок?
Разговор был о том, что QI — не аналог dynamic_cast, потому что применим даже когда не происходит приведение между указателями на разные интерфейсы одного и того же C++ - объекта. Я это доказал. Ты дальше бесишься тут.
неправильно посчитал ссылкиКоооод приведи.
Коооод приведи.
IBase* pb = new Component; // does addref once
IExtended* pe;
pb->QueryInterface(IID_EXTENDED, (void**)&pe);
pb->Release; // base class is deleted here
pe->Release; // access to freed memory
Думаешь я нормально не реализую подсчет ссылок?
В текущем коде ты его уже неправильно реализовал. Если ты планируешь и дальше обзывать всех вокруг лошарами, потрудись, чтобы такого дерьма в твоих постах не было. Можешь взять пример с кохтпы - он написал код 2-3 раза за всё время активных дискуссий в Development-е.
Разговор был о том, что QI — не аналог dynamic_cast, потому что применим даже когда не происходит приведение между указателями на разные интерфейсы одного и того же C++ - объекта. Я это доказал. Ты дальше бесишься тут.Не вопрос. Но я ещё раз повторяю, что QI в COM - это семантический аналог dynamic_cast, и что это не мои слова - я цитирую книжку.
pb->Release; // base class is deleted hereWhy do you think so?
Где это у меня в коде написано?
В текущем коде ты его уже неправильно реализовал.Неверно, потому что я не приводил реализации подсчета ссылок. В расширенной части привел (частично с ним какие проблемы?
pe->Release; // access to freed memoryЕсли прикапываться, то пример тоже плохой. Может у меня нигде не сказано, что базовый класс владеет этой расширенной частью и удалит его, когда сам удалится. Но тут спорить не буду, если подсчет ссылок реализовать тупо (так как ты предлагаешь мне реализовать то работа с pe будет логической ошибкой после вызова Release основного интерфейса. Но кто сказал, что я тебя послушаю и сделаю тупо?
Неверно, потому что я не приводил реализации подсчета ссылок. В расширенной части привел, с ним какие проблемы?Да, в расширенной части с ним проблемы. Он либо обращается к освобождённой памяти, либо является источником её утечки. Тот факт, что ты сейчас можешь всё это замазать, никак не поднимает твой уровень на высоту. Не разобравшись в теме (пусть она и оффтопик) не стоит лезть в дискуссию.
Да, в расширенной части с ним проблемы. Он либо обращается к освобождённой памяти, либо является источником её утечки. Тот факт, что ты сейчас можешь всё это замазать, никак не поднимает твой уровень на высоту. Не разобравшись в теме (пусть она и оффтопик) не стоит лезть в дискуссию.Приведи код. Твои слова что-то тут ведет к течке - это не доказательство. КОД в студию, или небыло =)
Но кто сказал, что я тебя послушаю и сделаю тупо?Мне неважно, как ты сделаешь, мне важно, как ты уже сделал:
class ExtPart : IExtended
{
private:
Component *cmpnt;
public:
ExtPart(Component *_cmpnt) : cmpnt( _cmpnt ) {}
HRESULT QueryInterface(...)
{
if (id == IID_EXTENDED)
{
*ppv = static_cast<IExtended*>( this );
} else if (id == IID_UNKNOWN) // тут честно не помню как называется константа GUID для IUnknown
{
*ppv = static_cast<IUnknown *>( cmpnt );
} else if (id == IID_BASE)
{
*ppv = static_cast<IBase *>( cmpnt );
} else ...
(reinterpret_cast<IUnknown *>( *ppv ->AddRef;
return S_OK;
}
void Release
{
if (--refcnt == 0)
{
cmpnt->pExt = 0;
delete this;
}
}
};
Тот факт, что ты сейчас можешь всё это замазатьЭто ты о чем?
Мне неважно, как ты сделаешь, мне важно, как ты уже сделал:Свой код напиши, который это демонстрирует. Пока то что ты написал выше я разнес в клочья. Тот код делает предположение, что во _внешнем_ классе подсчет ссылок выполнен _неверно_. Это предположение делать глупо.
как ты уже сделал:Еще раз повторяю. Тот код делает предположения о _внешнем_ классе. В нем реализацию AddRef/Release я не приводил.
Свой код напиши, который это демонстрирует. Пока то что ты написал выше я разнес в клочья. Тот код делает предположение, что во _внешнем_ классе подсчет ссылок выполнен _неверно_. Это предположение делать глупо.Если во внешнем классе он будет верным, то потечёт память. Потому что подсчёт ссылок сделан неверно во внутреннем.
Приведи код.
IBase* pb = new Component;
IExtended* pe;
pb->QueryInterface(IID_EXTENDED, (void**)&pe); // pb refcount == 2
pb->Release;
pe->Release; // pb was not freed
Это делает в принципе невозможной эту реализацию?
Для расширяемости можно возвращать строку или guid, но опять же, с точки зрения проектирования это параллельнАлександреску для расширяемости приводил пример с темплатными списками типов.
Ладно, надо добавить еще строчку в этот Release. Я писал в форуме сразу и не проверял работу этого всего.Следует добавить это в конструктор и деструктор, потому что в COM есть ряд правил работы с IUnknown (у меня в книжке их четыре). Одно из правил говорит о том, что надо вызывать AddRef в конструкторе, а не до его вызова. В общем-то до 2001-2003 года вся эта жЭсть была очень популярна на собеседованиях.
Реализация будет вполне возможна и безошибочна. И теперь, возвращаясь к Главному Вопросу оффтопика я могу лишь заметить, что dynamic_cast тоже может возвращать разные указатели (по значению).
Александреску для расширяемости приводил пример с темплатными списками типов.Александреску хорош, чтобы побаловаться. Но потом писать write only код надоедает. Впрочем, это всё тема холи вара.
я могу лишь заметить, что dynamic_cast тоже может возвращать разные указатели (по значению).Ну и причем тут это-то? Они будут указывать на один объект все равно. Тут и не в dynamic_cast дело, и обычное неявное преобразование (если есть множественное наследование) указателя или ссылки на производный класс к базовому может изменить значение указателя.
Зри в корень, не надо распыляться на мелочи. Например когда я реализовывал эти QIs я основное внимание уделял на то, чтобы не ошибиться при возвращении правильного указателя на IUnknown, а не на подсчет ссылок, отнесясь к нему халатно. Ты ведь и не просил реализацию подсчета ссылок.
С оффтопиком разобрались: в COM _возможно_ реализовать компонент несколькими объектами C++, каждый из которых реализует часть интерфейсов целого компонента, хотя и не так просто, как кажется на первый взгляд.
Если смотреть на этот процесс как на повторное использование компонента, реализующего эти дополнительные интерфейсы, то этот процесс называется агрегированием и для него даже есть поддержка системы.
Чтобы подытожить аналогию: все-таки QI - не семантический, а синтаксический аналог dynamic_cast: он преследует те же цели (понять, возможно ли выполнять некоторые действия с компонентом динамически, во время выполнения программы но делает это по-другому: в одном случае мы остаемся в рамках одного объекта C++, что гарантируется компилятором (dynamic_cast в другом — реализация метода и поддержание его корректности ложится на программиста, что дает большую свободу и больше возможностей, но и предоставляет большее пространство для ошибки.
Что касается enum'ов:
признает, что у них есть недостатки.
утверждает, что при использовании enum'ов вместо dynamic_cast как-то изменяется зависимость (class coupling) между классами системы, но в то же время утверждает, что эти два решения эквивалентны с точки зрения модели. Этот момент требует разъяснений.
Тот же, кто пройдётся по коду у тебя и поправит dynamic_cast-ы в случае, когда для B и C надо совершать разные действия.Нет, придется только если мне нужна специальная функциональность для C. А если меня устраивает работа с C как с B, то _ничего_ абсолютно менять не придется во внешнем коде. В случае же enum'ов придется походить, поисправлять.
все-таки QI - не семантический, а синтаксический аналог dynamic_castСинтаксический? В Си++? Это как? Кстати дальше ты описываешь схожую семантику , а не синтаксис.
Нет, придется только если мне нужна специальная функциональность для C. А если меня устраивает работа с C как с B, то _ничего_ абсолютно менять не придется во внешнем коде. В случае же enum'ов придется походить, поисправлять.Это не обязательно. Я могу оставить тоже самое значение enum-а.
Пойми одну простую вещь: везде ходить не придётся. Решение с dynamic_cast/enum пригодно только в одном случае - когда операция с конкретными типами одна. В остальных случаях в Си++ надо использовать паттерн Visitor. Собственно в первом моём посте в этой теме об этом и говорится.
Решение с dynamic_cast/enum пригодно только в одном случае - когда операция с конкретными типами одна.так при хорошей архитектуре классов операция как раз одна.
в этом-то и смысл полиморфизма, чтобы сделать классы так, чтобы клиент разнородные объекты видел как однородные.
так при хорошей архитектуре классов операция как раз одна.Как это операция одна, если их может быть несколько?
в этом-то и смысл полиморфизма, чтобы сделать классы так, чтобы клиент разнородные объекты видел как однородные.
1. Напечатать погоду.
2. Покомандовать всплытием подводной лодки.
3. Выдать погоду в телетекст.
и т.д.
Как это операция одна, если их уже несколько?это примений несколько, а в каждом случае при хорошей архитектуре вариант обработки один
1. Напечатать погоду.std::cout << weather.Display << std::endl;
2. Покомандовать всплытием подводной лодки.if (weather.IsGood
submarine.Up;
3. Выдать погоду в телетекст.teletext << weather.Display << std::endl;
и никаких визитеров не надо.
std::cout << weather.Display << std::endl;В телетекст и на консоль выводить надо с разным форматированием.
if (weather.IsGood
submarine.Up;
IsGood для одного типа подлодки возвращает true, а для другого в этих же условиях должен вернуть false.
Теперь каждый класс погоды разрастается для всех операций. Добавление новых операций усложнено. У тебя что, нету книжки GoF? Там всё расписано.
я правильно понял?
new CoutWeatherVisitor(cout)->Visit(weather);
new IsGoodWeatherVisitor(submarine)->Visit(weather);
new TeletexWeatherVisitor(teletex)->Visit(weather);
да, и можно несколько примеров из каких-нибудь стандартных библиотек твоего подхода?
Теперь каждый класс погоды разрастается для всех операций.значит надо сделать их внешними, да и всё
std::cout << FormatForCout(weather) << std::endl;
if (IsGoodForUp(weather, submarine
submarine.Up;
teletex << FormatForTeletex(weather) << std::endl;
а типа такой код - это верх удобства и расширяемости?Visitor не является верхом удобства в языках без multiple dispatch. Все его минусы описаны в книжке, и я не понимаю, почему ты их выясняешь у меня на форуме. Почитай книжку GoF там всё детально разъяснено. Эта книжка для меня является авторитетом, а на что ссылаешься ты, я пока что понять не могу.
Да, такой код более расширяем, потому что отделяет операцию от типов, с которыми эта операция работает. Он позволяет без проблем добавлять новые операции. Это написано в описании паттерна. Это действительно работает и проверено на практике.
да, и можно несколько примеров из каких-нибудь стандартных библиотек твоего подхода?
Можно - почитай код стандартных библиотек Java, и код часто используемых библиотек на Java. Тот факт, что Microsoft переманила рынок дельфистов и только к 2008 году разродилась на MVC и другие технологии, которые в других платформах существуют уже много лет, ни о чём мне не говорит.
Подход не мой - подход разработали другие люди и написали про него книжку.
значит надо сделать их внешними, да и всёДа, и в каждой из них ты предлагаешь писать typeof решения для конкретизации типа погоды. Я уже показал в этой теме, что эти решения хуже Visitor-а (впрочем, всего-лишь повторив сказанное в книжках и статьях).
std::cout << FormatForCout(weather) << std::endl;Ещё раз замечу, что решение с Visitor выглядит в точности так же. Здесь интересно смотреть внутрь функции, где ты предлагаешь эмулировать полиморфизм через typeof Это приемлемо, если требуется одна операция. Но если требуются несколько операций и будет добавление новых - не подходит.
Подход не мой - подход разработали другие люди и написали про него книжку.реальные примеры, давай.
раз ты говоришь, что это широко зарекомендовавший себя способ - тебе легко их будет вспомнить и найти.
реальные использования visitor-ов есть только в коде аля парсеры/генераторы, где кол-во подклассов жестко конечно.
во всех остальных случаях, примеров визиторов не наблюдается.
> Эта книжка для меня является авторитетом
А если эта книжка не является авторитетом, тогда что?
Если хорошесть погоды зависит от подводной лодки, то она
fn: boat -> weather -> bool или fn: boat * weather -> bool,
безо всяких искусственных конструкций.
---
"Прямая --- короче, парабола --- круче."
реальные примеры, давай.Почему я должен давать какие-то примеры? Я сослался на литературу, а ты - нет. Вот сам и давай реальные примеры, где много операций над фиксированной иерархией сделано без Visitor.
раз ты говоришь, что это широко зарекомендовавший себя способ - тебе легко их будет вспомнить и найтиДа, мне легко их найти: NHibernate, spring framework, Open Inventor, JUnit. Я уже молчу о действительно огромном количестве Visitor-ов в различных parser-ах, где не использовать этот паттерн вообще является плохим тоном.
А если эта книжка не является авторитетом, тогда что?Тогда надо сослаться на другой источник или сказать, что "я сам себе источник и авторитет". Чтобы читатели темы могли сделать выводы.
безо всяких искусственных конструкций.В этой теме, если ты удосужился прочитать, никто не спорил, что Visitor возникает из-за отсутствия multiple dispatch в языках Си++/C#/Java.
Вот сам и давай реальные примеры, где много операций над фиксированной иерархией сделано без Visitor.как минимум я весь тред утверждал, что очень узкий класс приложений имеет классы с фиксированной иерархией.
и, например, погода (в данном случае) - скорее с нефиксированной иерархией, чем с фиксированной.
тем более, если иерархия фиксированная, то ее бывает удобнее заменить на один класс, чем возиться с иерархией классов.
основные примеры фиксированной иерархии являются парсеры/генераторы (очень я и упомянул в предыдущем посте).
так вот можно реальные примеры применения визиторов не для парсеров/генераторов?
или хотя бы реальные примеры применения фиксированных иерархий не для парсеров/генераторов?
как минимум я весь тред утверждал, что очень узкий класс приложений имеет классы с фиксированной иерархией.Можно ссылки? Я выполнил поиск по слову "иерархия" и что-то не нашёл. О реальной проблеме с иерархией упоминал только unkulunukululu. Ты весь тред не утверждал того, что написал сейчас.
Кстати, можно мне примеры реальных приложений, где классы не имеют фиксированную иерархию? На C# это ведёт как минимум к генерации кода на лету, как я понимаю.
тем более, если иерархия фиксированная, то ее бывает удобнее заменить на один класс, чем возиться с иерархией классов.
А всю программу удобнее заменить на MainClass, чтобы вообще ни с чем не возиться.
основные примеры фиксированной иерархии являются парсеры/генераторы (очень я и упомянул в предыдущем посте).
Почему эти примеры основные, а другие примеры - не основные? Например: интерпретаторы, библиотеки для работы с графами, загрузка различных типов из базы данных, иерархия признаков для статистического анализа, иерахия элементов трёхмерной модели, иерархия позиций в списке контактов в instant messenger, иерархия ответов от сервера в объектно-ориентированном протоколе, и так далее.
или хотя бы реальные примеры применения фиксированных иерархий не для парсеров/генераторов?Реальные примеры можно привести даже для погоды. Очевидно, что её модель может иметь ограниченный набор физических свойств, которые будут иметь фиксированную иерархию.
> Тогда надо сослаться на другой источник
Питер Норвиг подойдёт?
>> безо всяких искусственных конструкций.
> В этой теме, если ты удосужился прочитать, никто не спорил,
> что Visitor возникает из-за отсутствия multiple dispatch в
> языках Си++/C#/Java.
Мне покласть на частные проблемы какого-то насильно пропихиваемого подхода.
Твой "visitor" работает в любом языке с функторами, даже в сях. Более того,
тот, кто работал с сетью в операционных системах, уже знает, как это делается.
Первоначальный вопрос, вообще, был про моделирование, вернул обсуждение
в то состояние, которое было в самом начале, и привёл точно такой же пример,
какой следует из точки зрения, высказанной тов. .
---
"...Физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Питер Норвиг подойдёт?
Нет, если бы ты читал тему, то понял бы, что не подойдёт. Здесь половина постов о том, как лучше конкретизировать абстракцию. На П. Н. ссылайся в ответ на первый пост автора, и заодно скажи ему, что вместо Си++ ему надо использовать CLOS, OCaml, и так далее. Помощи и смысла от этого ответа будет ноль, но в этом вся твоя сущность в этом разделе форума.
Мне покласть на частные проблемы какого-то насильно пропихиваемого подхода.А мне покласть на тебя и твой тупой снобизм. Тебя в этот раздел никто не звал с твоими бессмысленными бреднями, которые мало связаны с темой; с неработающим кодом; и с ошибками, которые ты отказываешься признавать даже тогда, когда они очевидны.
Кстати, можно мне примеры реальных приложений, где классы не имеют фиксированную иерархию? На C# это ведёт как минимум к генерации кода на лету, как я понимаю.любое winforms приложение/библиотека.
иерархия классов наследуемых от контрола - необозримая
любое приложение/библиотека работающие с коллекциями
иерархия классов, наследуемая от интерфейса коллекции, тоже необозримая.
иерархия классов наследуемых от контрола - необозримоеПравильно, вот и плюёмся мы на всякие GridGrouping контролы от DevExpress и Syncfusion. От классов, которые имеют интерфейсы более 1000 (у Syncfusion) методов и свойств. Это не пример очень хорошего ООП решения, а пример того, как делать не надо. И я бы сказал, что Windows Forms - это как раз и есть ограниченное количество задач с нефиксированной иерархией.
любое приложение/библиотека работающие с коллекциямиДа? То есть ты в каждой своей программе наследуешься от интерфейса коллекции? Готовые не используешь? И почему оно там необозримо? Они каждый день добавляют новый способ сделать IList?
иерархия классов, наследуемая от интерфейса коллекции, тоже необозримая.
И так, у нас есть два так-себе примера, потому что модели в них слабо связаны с изначальной постановкой задачи. Я на 100% согласен, что Visitor там никуда не впился, ровно как и задача конкретизации абстракции. Это ты называешь "большинством"?
> когда они очевидны.
Здесь у меня ошибок нет.
Тебе не нравится то, что твои переусложнённые конструкции никому
не нужны? Ну так, извини, они и правда никому не нужны, кроме
тебя самого. Это тебе надо гордиться знанием "шаблонов,"
половина из которых являются просто функторами, и способностью
написать код "правильно," так, как завещал Гамма или кто там был.
Ещё раз: для задач, изложенных в первом сообщении, достаточно
возвращать сырые данные в виде записи, структуры, списка
ассоциаций и чего угодно, это уже определяется фантазией.
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
И так, у нас есть два так-себе примера, потому что модели в них слабо связаны с изначальной постановкой задачи. Я на 100% согласен, что Visitor там никуда не впился, ровно как и задача конкретизации абстракции. Это ты называешь "большинством"?Давай определимся более конкретно. Нам нужны примеры задач, где
1. Не фиксированная, постоянно меняющаяся иерархия классов.
2. Требуется добавлять операции над конкретными типами из иерархии.
3. Изменение иерархии происходит чаще, чем добавление новых операций.
Здесь у меня ошибок нет
Здесь нет ошибок, но есть бесполезность и снобизм. Они всегда у тебя есть, а в этом разделе - только они и есть.
Тебе не нравится то, что твои переусложнённые конструкции никомуМне абсолютно пофиг на то, какими сложными ты считаешь: ООП, императивные языки, GoF. Твоё личное мнение - это твоё личное мнение. Оно в данный момент, как обычно, бесполезно.
не нужны?
Ещё раз: для задач, изложенных в первом сообщении, достаточноЕщё раз: ты не понял ни задачу, ни того, что обсуждают в этой теме.
возвращать сырые данные в виде записи, структуры, списка
ассоциаций и чего угодно, это уже определяется фантазией.
Да? То есть ты в каждой своей программе наследуешься от интерфейса коллекции? Готовые не используешь? И почему оно там необозримо? Они каждый день добавляют новый способ сделать IList?потому что каждая либа добавляет свой вариант коллекции.
из банального:
дети у control,
rows у datatable-а,
columns у datatable-а,
tables у dataset-а,
entityset-у у data.linq,
коллекции с отложенным поведением у linq,
и т.д.
потому что каждая либа добавляет свой вариант коллекцииЗначит, я не до конца понял твой пример. В нём нету задачи конкретизировать абстракцию. Каждый клиент использует какую-то свою конктетную реализацию для конкретной цели. Здесь нет задачи, описанной автором:
Как будет в дальнейшем использоваться объект "погода", я не знаю, потому что это библиотека. Может, в веб-приложении отрисовываться будет, может всплытием подводной лодки командовать.Её (задачи) нет, потому что операции с коллекцией фиксированы. Не требуется реализовывать всё новые и новые способы обойти коллекцию, или вставить в неё элемент. Да ещё и такие, которые бы зависели от конкретного типа этой коллекции. Поэтому Visitor тут не применим и не нужен.
Давай определимся более конкретно. Нам нужны примеры задач, гдеwinforms,
1. Не фиксированная, постоянно меняющаяся иерархия классов.
2. Требуется добавлять операции над конкретными типами из иерархии.
3. Изменение иерархии происходит чаще, чем добавление новых операций.
коллекции,
stream-ы,
файлы (файл бывает в виде: path, fileinfo, stream, binaryreader/binarywriter, textreader/textwriter, xmlreader/xmlwriter
исключения,
авторизация,
права доступа,
элементарные типы данных (int, string и т.д.
способы связи(каналы
графические примитивы,
виды документов,
и т.д.
Её (задачи) нет, потому что операции с коллекцией фиксированы. Не требуется реализовывать всё новые и новые способы обойти коллекцию, или вставить в неё элемент. Да ещё и такие, которые бы зависели от конкретного типа этой коллекции. Поэтому Visitor тут не применим и не нужен.все наоборот.
конечно же операций над коллекциями как грязи(выборки, пересечения, группировки, фильтрации, синхронизации и т.д.) и они не фиксированы.
и есть куча библиотек стандартных и полустандартных для работы с коллекциями.
в C++ - - это как минимум <algorithm>,
в C#(из нового) - это system.linq.
Да ещё и такие, которые бы зависели от конкретного типа этой коллекциии это тоже не так.
т.к. хотя бы банальная задача - получить кол-во элементов в коллекции - зависит от того, что перед нами - список(IEnumerable коллекция(ICollection массив, ассоциативный массив, частная коллекция и т.д.
> Здесь нет ошибок, но есть бесполезность и снобизм.
> Они всегда у тебя есть, а в этом разделе - только они и есть.
Зато ты очень помог своим "конкретным" советом. Да так, что вы
обсуждаете отлечённые вопросы уже две сотни сообщений.
> Ещё раз: ты не понял ни задачу, ни того, что обсуждают в этой теме.
Ещё раз: я прекрасно понял задачу. То, что вы обсуждаете,
к ней относится только в по-вашему объектно-ограниченной
разновидности проектирования.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
Зато ты очень помог своим "конкретным" советом. Да так, что выДа, я помог, а дальше мы это обсуждаем. Это и называется - дискуссией, спором, и т.п.
обсуждаете отлечённые вопросы уже две сотни сообщений.
Ещё раз: я прекрасно понял задачу. То, что вы обсуждаете,
к ней относится только в по-вашему объектно-ограниченной
разновидности проектирования.
Ещё раз: ты не понял задачу.
winforms,Не требуется добавлять новые операции в зависимости от реализации.
коллекции
Не требуется добавлять новые операции в зависимости от реализации.так что system.linq тогда делает?
все наоборот.Ты перепутал алгоритмы и операции. Все операции, которые можно сделать со всеми реализациями списка уже определены в интерфейсе IList. И число этих операций не растёт, а строго фиксированно вот уже несколько лет.
конечно же операций над коллекциями как грязи(выборки, пересечения, группировки, фильтрации, синхронизации и т.д.) и они не фиксированы.
и есть куча библиотек стандартных и полустандартных для работы с коллекциями.
в C++ - - это как минимум <algorithm>,
в C#(из нового) - это system.linq.
и это тоже не так.Это всего лишь недостатки реализации коллекций в .NET, причём не единственные. Ты привёл пример оптимизации алгоритма получения количества элементов в перечислении, а не добавление новой операции.
т.к. хотя бы банальная задача - получить кол-во элементов в коллекции - зависит от того, что перед нами - список(IEnumerable коллекция(ICollection массив, ассоциативный массив, частная коллекция и т.д.
так что system.linq тогда делает?Вызывает extension методы, которые по сути являются статическими методами, что-то делающими с коллекцией. Понимаешь, сравнение "i < weather.Temperature" или обход коллекции от последнего к первому элементу - это не добавление новой операции в интерфейс погоды или в интерфейс коллекции.
это не добавление новой операции в интерфейс погодытак и вывод погоды - это уж точно тогда не новая операция над погодой.
а вывод погоды на экран - это новая операция?
я правильно понял твою изощренную логику?
так и вывод погоды - это уж точно тогда не новая операция над погодой.Сам по себе вывод - уж точно не новая операция над погодой. А вот вывод погоды в той задаче, которую описал автор - в базовом классе нет метода, который есть в дочернем, - это уже операция. Это потребует изменения интерфейса базового класса. Таким образом, либо базовый класс будет содержать все методы, частично реализованные в дочерних классах; либо нам нужно будет для каждой такой операции конкретизировать тип базового класса. Таких задач полно и без парсинга или сериализации.
т.е. вывод коллекции на экран - это не новая операция,Новая операция в данной теме - то, что требует изменения интерфейсов в иерархии классов. В том коде, который привёл автор есть коментарий "Новый метод, нету в предке!"
а вывод погоды на экран - это новая операция?
я правильно понял твою изощренную логику?
Чтобы обойти список или получить его элемент вовсе не требуется менять интерфейс IList.
Новая операция в данной теме - то, что требует изменения интерфейсов в иерархии классов. В том коде, который привёл автор есть коментарий "Новый метод, нету в предке!"так это часто бывает следствием плохой архитектуры классов.
Чтобы обойти список или получить его элемент вовсе не требуется менять интерфейс IList.это следствие хорошей архитектуры IList-а.
ps
т.е. я к тому, что если хочется сделать визитор, то скорее всего это означает, что иерархия классов была плохо спроектирована.
так это часто бывает следствием плохой архитектуры классов.Это часто бывает следствием хорошей архитектуры классов. Собака является животным. Кошка является животным. Только собака гавкает, а кошка мяукает.
это следствие хорошей архитектуры IList-а.Это всего лишь следствие того, что новые операции в этот интерфейс добавлять не надо.
т.е. я к тому, что если хочется сделать визитор, то скорее всего это означает, что иерархия классов была плохо спроектирована.Да? То есть единственно возможная иерархия - это когда базовый класс содержит все методы всех дочерних классов? Или когда дочерние классы не имеют новых методов и полей?
>> обсуждаете отлечённые вопросы уже две сотни сообщений.
> Да, я помог, а дальше мы это обсуждаем. Это и называется -
> дискуссией, спором, и т.п.
"У тебя стоит задача конкретизировать абстракцию,"--- это не
помощь, ты обсуждаешь это уже две сотни сообщений и конца не
видно.
> Ещё раз: ты не понял задачу.
Раз для тебя программирование сводится к "кодингу,"
то, пожалуйста, да, я не понял задачу. У меня нет
такой, как у тебя, привычки сразу бросаться "копать,"
не вникнув в свойства предметной области.
Поэтому я и считаю, заметь --- не один, что "погода" должна быть
простым агрегатным типом. Каким конкретно будет этот тип, зависит
от реализатора, если он любитель какой-нибудь "Boost," он может
хоть таблицы задействовать. А вы все бросились обсуждать какие-то
частные проблемы некоторой реализации объектно-ориентированной
парадигмы. Последнее задающему вопрос наврядли интересно.
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
"У тебя стоит задача конкретизировать абстракцию,"--- это неА ты попробуй прочитать весь пост, а не одно предложение из него.
помощь, ты обсуждаешь это уже две сотни сообщений и конца не
видно.
Поэтому я и считаю, заметь --- не один, что "погода" должна быть
простым агрегатным типом.
Ещё раз: ты не понял задачу. Заметь, что погода была просто примером, высосанным из пальца. Автор даже дописал коментарии, чтобы было понятно, что именно за задачу он решает. Потрудись почитать посты других людей до того, как лезть в дискуссию.
Я его прочитал несколько раз, нет там никакой помощи, потому что
всё упирается в заявления, основанные на неправильном проектировании.
Ну, да, ты начал копать яму здесь, ну и что?
> Заметь, что погода была просто примером, высосанным из пальца.
> Автор даже дописал коментарии, чтобы было понятно, что именно
> за задачу он решает.
Ему и на это указали, чтобы привёл реальный пример.
Заметь, опять не я.
Так что все ваши рассуждения являются отвлечёнными, основанными
на такой же или похожей ошибке проектирования. Ну и где здесь
помощь?
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Я его прочитал несколько раз, нет там никакой помощи, потому чтоНа проектировании чего? Прочитай пост автора.
всё упирается в заявления, основанные на неправильном проектировании.
Ему и на это указали, чтобы привёл реальный пример.
Когда приведёт реальный пример, тогда и будем проектировать. Сейчас приведён пример реальной задачи, которую я решил.
Так что все ваши рассуждения являются отвлечёнными, основанными на такой же или похожей ошибке проектирования. Ну и где здесь помощь?Помощь здесь в том, что решена задача, поставленная автором. Если просят сложить 1+1, то только ты здесь потребуешь общую задачу, все остальные напишут 2.
Да? То есть единственно возможная иерархия - это когда базовый класс содержит все методы всех дочерних классов?нет.
хорошая иерархия - это когда уже базовые интерфейсы содержит достаточный набор операций для того, чтобы можно было успешно работать с произвольным дочерним объектов без особой потери функциональности.
если брать ту же погоду, то может быть базовый класс просто должен иметь интерфейс, который позволяет получить все состояние в виде набора пар: название, значение.
обе задачи вывода - это бы точно покрыло, и не требовались бы никакие визиторы.
с субмариной чуть сложнее, но в первую очередь потому что я не представляю, что именно там от погоды должно требоваться.
ps
если переходить к сути, то я о том, что надо для начала представить как бы выглядели операции вывода погоды, использования погоды в субмарине и т.д.
а уже потом под эти операции разработать такой набор интерфейсов - которые делают такое использование удобным - при чем без всяких визиторов.
хорошая иерархия - это когда уже базовые интерфейсы содержит достаточный набор операций для того, чтобы можно было успешно работать с произвольным дочерним объектов без особой потери функциональности.Мне очень интересно, как ты себе это представляешь? В виде набора ключ - значение? Отличное решение, только уже без ООП. Такая странная эмуляция динамического типа. Может быть, даже прикрутить к ней Reflection Emit?
если брать ту же погоду, то может быть базовый класс просто должен иметь интерфейс, который позволяет получить все состояние в виде набора пар: название, значение.Может быть, а может и не быть. Если в задаче есть методы, относящиеся к конкретным типам. Такое в ООП встречается постоянно. Я даже не вижу смысла об этом спорить.
если переходить к сути, то я о том, что надо для начала представить как бы выглядели операции вывода погоды, использования погоды в субмарине и т.д. а уже потом под эти операции разработать такой набор интерфейсов - которые делают такое использование удобным - при чем без всяких визиторов.Ага, и получить менее расширяемое для данной задачи решение. Потому что для создания новой операции, которая вдруг не вписалась в базовый интерфейс, придётся менять все дочерние типы. В посте автора есть конкретное указание на эту проблему. (Я всё ещё считаю что пример про погоду он привёл от балды.)
Интерфейса библиотеки. Автору указали, что надо отдавать "struct,"
если он считает, что его задача требует именно такого интерфейса,
как указывал он сам, то он это никак не обосновал.
"Погода" на самом деле простая запись, её незачем делить на
вымышленные подклассы.
> Когда приведёт реальный пример, тогда и будем проектировать.
> Сейчас приведён пример реальной задачи, которую я решил.
То есть, ты считаешь, что если этот пример --- погода,
проектировать не обязательно, а если это какая-то отвлечённая
погода, то надо не требовать уточнить пример, а кидаться
"помогать" не менее абстрактными советами?
Ладно, я понял, в чём заключается твоя "помощь."
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
"Погода" на самом деле простая запись, её незачем делить наЕщё раз: ты не понял задачу.
вымышленные подклассы.
То есть, ты считаешь, что если этот пример --- погода, проектировать не обязательно
Необязательно, потому что этот пример мог быть для того, чтобы поставить задачу. Всё это обсудили в этой ветке. Но этого не понял только ты, прилямбдованный насильник.
Ладно, я понял, в чём заключается твоя "помощь."
Она заключается в конкретных вещах. В отличие от твоей бесполезной воды, от которой всех тошнит. Иди дальше прозябай от своей бесполезности для общества со своими терабайтами памяти в голове, которыми крутит микроконтроллер с частотой 1 КГц.
Не парь мозг, возвращай структуру или что-нибудь подобное.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
Дайте знать, когда придете к консенсусусоглашусь с контра-ой. не знаешь как делать - делай просто(и открыто) - одна структура на все виды погоды для начала будет самое-то.
вот когда будет в этой структуре 40 мемберов, будешь путаться - когда поле имеет смысл, а когда не имеет- тогда можно думать сделать иерархию классов.
Сможешь привести пример хотя бы трёх таких параметров?
---
"Истина всегда конкретна."
соглашусь с контра-ой. не знаешь как делать - делай просто(и открыто) - одна структура на все виды погоды для начала будет самое-то.[-style]
вот когда будет в этой структуре 40 мемберов, будешь путаться - когда поле имеет смысл, а когда не имеет- тогда можно думать сделать иерархию классов.
---
Преждевременная оптимизация — это корень зла.
Дональд Э. Кнут
[/-style]
Сможешь привести пример хотя бы трёх таких параметров?направление разыгравшегося торнадо
толщина ледяного покрова в озерах
размер снежинок
Это не входит в понятие "погода."
> толщина ледяного покрова в озерах
Это вообще чушь.
> размер снежинок
Это могло бы подойти, но оно не измеряется на метеоплощадке.
Учи матчасть.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."
никто не обещал, что погода будет измеряться на метеоплощадке, а не с помощью кидания барометра в воду.
По поводу полей вообще всем уже давно ясно, что инкаспуляция решает и делать/не делать поля вопрос не стоит. Не?
По поводу полей вообще всем уже давно ясно, что инкаспуляция решает и делать/не делать поля вопрос не стоит. Не?инкапсуляция важна на стыках.
соответственно - если это класс для явный стыковки с внешним кодом, к которому мы не имеем доступа на изменение и перекомпиляцию, то да - полей лучше не делать.
если же - использующий код наш же, или мы имеем доступ на его изменение, то инкапсуляция сама по себе не очень важна. важнее при выполнение кода придерживаться правила - при написании куска кода закладывайся по минимуму (как можно меньше накладывай требований) на другие куски кода.
Если никто не обещает, что погода будет погодой, разговаривать не о чем.
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Неверно.
---
"Математика --- лучший способ водить самого себя за нос."
Если никто не обещает, что погода будет погодой, разговаривать не о чем.в каждой задаче свое понимание погоды.
раз возможны следующие диалоги, значит перечисленные мной поля можно относить к погоде.
- что у нас с погодой?
- на нас движется торнадо.
- что у нас с погодой?
- лед потаял, и составляет в среднем 3 см. рыбалка со льда не рекомендуется, сэр.
- что у нас с погодой?
- идет снег мелкой крупой.
если же - использующий код наш жеЯ понимаю, что в этом треде в некотором роде модно уходить от темы, но в первом посте написано, что это не так =)
Я понимаю, что в этом треде в некотором роде модно уходить от темы, но в первом посте написано, что это не так =)там не сказано, приложения наши же или нет.
> По поводу полей вообще всем уже давно ясно, что инкаспуляция решаетКогда пишешь библиотеку надо по возможности изолировать пользователей от реализации. С чем ты не согласен? Или с чем не «все» согласны?
Неверно.
Где ты в быту видел "классы," да ещё с какими-то наследованиями и иерархиями?
Может, ты ещё и числа видел?
Учи матчасть.
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Я понимаю, что в этом треде в некотором роде модно уходить от темы, но в первом посте написано, что это не так =)если же мы говорим все-таки про библиотеку, то начинать надо, конечно, с кода использования, а не с кода самой библиотеки.
там не сказано, приложения наши же или нет.Мне казалось, что сказано, что автор довольно четко отделен от клиентов. Т.е. не знает их намерений. А этом случае лучше перестраховаться. Все-таки погода — довольно абстрактный объект, чтобы реализовывать его просто структурой с открытыми полями.
Где ты в быту видел "классы," да ещё с какими-то наследованиями и иерархиями?в речи, как минимум.
> пользователей от реализации.
Ты не задумывался о том, что, если ты закроешь доступ к
температуре и атмосферному давлению, то, что ты отдаёшь,
перестанет быть состоянием тропосферы, сиречь погодой?
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Ты не задумывался о том, что, если ты закроешь доступ кЯ их открою через методы доступа (или свойства в более современных языках). Это позволит мне завтра (когда человечество более точно постигнет погоду ) вычислять эти параметры через другие. Это раз. Два: я чисто технически избавлюсь от необходимости в каждом месте контролировать корректность данных («Температура минус миллион градусов»). И много-много еще плюсов.
температуре и атмосферному давлению, то, что ты отдаёшь,
перестанет быть состоянием тропосферы, сиречь погодой?
Где ты в быту видел "классы," да ещё с какими-то наследованиями и иерархиями?Слабый аргумент. Нас окружают объекты. Язык программирования такая же абстракция, как и числа.
Может, ты ещё и числа видел?
Учи матчасть.
Это позволит мне завтраэто разве не "преждевременная оптимизация"?
это разве не "преждевременная оптимизация"?Когда писал об этом подумал. Но это слишком примитивная замена x.f на x.GetF и x.f = 7 на x.SetF( 7 которая не обещает стремных последствий. Прямой доступ к полям обещает гораздо более неприятные последствия. Почему-то подумал о синхронизации. Но это все последствия недавнего ожога, вызванного многопоточной неопытностью =)
PS. спокойной ночи
Нас не окружают отвлечённые предметы, поэтому мы можем
моделировать действительность разными способами.
Но из-за того, что мы допускаем при моделировании какие-то
потери, совсем не следует, что мы можем допускать произвольно
большие потери.
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Какие? Температура и давление --- первичные данные.
Вам это должны были объяснять в пятом классе на уроках географии.
Или при вас её уже отменили?
> я чисто технически избавлюсь от необходимости в каждом месте
> контролировать корректность данных
Ты не можешь получить такие данные чисто практически,
никакой теории для этого не требуется.
---
"Математик может говорить, что ему хочется,
но физик должен, хотя бы в какой-то мере, быть в здравом рассудке."
Прямой доступ к полям обещает гораздо более неприятные последствия.но все это наивная вера, что мы сегодня угадали, какой завтра нам захочется интерфейс (какое нам завтра захочется взаимодействие)
соответственно, если мы уже имеем большой опыт эксплуатации библиотеки - то тогда да, тогда уже есть фундамент, на котором можно планировать дальнейшее развитие.
и можно уже прикинуть - что вот здесь хотим поля, это хотим вот в виде вот таких абстрактных классов и т.д.
Оставить комментарий
Realist
Положим, я пишу библиотеку, которая возвращает текущую погоду за бортом для использования в других приложениях.То есть у меня есть функция, которая возвращает либо базовый класс, либо одного из потомков. Как будет в дальнейшем использоваться объект "погода", я не знаю, потому что это библиотека. Может, в веб-приложении отрисовываться будет, может всплытием подводной лодки командовать.
Как мне правильно реализовать и использовать библиотеку? Пробовать делать dynamic_cast на результат? Добавить метод getType? Добавить в предка simpleWeather заглушку для getRainIntensity?