Как стать автором
Обновить

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

Господи, как в 90е… используйте тег source lang=«cpp»
А всё написаное обычный процесс переработки подробно описаный многими людьми, например, неплохо описано это, у Роберта Мартина в его книге «Чистый код».
Уже лучше)
Будем наедятся что часть программистов хотя бы тут узнают про основы рефакторинга и начнут писать более чистый и понятный код. Продолжайте в том же духе.
Когда прочел первый код сразу задался вопросом почему не сразу решили задачу как в последнем коде? Ведь это очевидное решение, которое описано во всех книгах по С++? Хотя для новичков такое решение не очевидно, они чаще всего не понимают для чего нужны виртуальные функции, особенно чистые виртуальные. В этом плане статья хорошо показывает как нужно правильно писать такие интерфейсы, хотя и сильно растянута. В своей практике иногда встречаю код подобный первому в статье, и делаю замечание разработчикам, в ответ получаю, «что ты прав, но мне сейчас некогда, потом переделаю» и т. д. В результате все остается как было и что либо расширить уже довольно сложно.
Статья такая растянутая потому, что хотелось ответить на много вопросов. Это своеобразная статья-ответ. Я вообще хотел её в личный блог засунуть, но перечитал, посмотрел… как-то складно вроде получилось, можно и людям показать :-) Наверняка кто-то что-то да почерпнёт для себя.
тем кто переходит с процедурных языков такое решение не очень очевидно, но по моему для обучения этому есть ВУЗы…
В вашем последнем коде в классах потомках в описаниях функций вы не указали virtual, хотя это и не обязательно, но мне кажется что нужно ставить, чтобы было ясно что они виртуальные и тогда не нужно лезть в описание предка и смотреть какие они на самом деле.
НЛО прилетело и опубликовало эту надпись здесь
Вы серьёзно думаете, что все, кто прочитал этот пост тут же пошлют резюме в Яндекс?

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

Просто я бы мог написать «мне по работе попалась вот такая задача и я её решил так». Но «вот такую задачу» долго рассказывать, а тут есть уже готовые вопросы на которые можно просто дать ссылку.
НЛО прилетело и опубликовало эту надпись здесь
Я всё же настаиваю на том, что это не решения. Это мои собственные мысли. Я не являюсь сотрудником Яндекса, я не располагаю инсайдерской информацией, я не знаю правильных решений… и судя по размерам полей ввода, Яндекс ждёт не таких ответов.
НЛО прилетело и опубликовало эту надпись здесь
Я хочу пройти собеседование в Яндексе, но не хочу слать резюме…
Я бы сказал, что в конкретном варианте даже полезен compile time полиморфизм, для этого есть совсем неплохая либа boost::mpl, есть попроще Loki, да и самому здесь тоже писать очень мало, зато все отсеится на этапе компиляции и не будет лишнего кода. Когда-то клепал примерчик:

#include <iostream>

class Foo
{
public:
int GetValue()const{return 1;}
};

class Bar
{
public:
int GetValue()const{return 2;}
};

template<int v>
struct Int2Type
{
enum {value = v};
};

template<typename T>
struct TypeTrait
{
enum {IsValid = 0};
};

template<>
struct TypeTrait<Foo>
{
enum {IsValid = 1};
};

template<typename T>
inline void _DoWork(const T& t, Int2Type<1>)
{
std::cout << "value: " << t.GetValue() << std::endl;
}

// ошибка, тип не поодерживается
// либо вообще убрать данную функцию, в этом случае будет ошибка компиляции на DoWork(bar);
template<typename T>
inline void _DoWork(const T& t, Int2Type<0>)
{
// в идеале здесь требуется статическая проверка этапа компиляции
std::cout << "error" << std::endl;
}

template<typename T>
inline void DoWork(const T& t)
{
_DoWork(t, Int2Type<TypeTrait<T>::IsValid>());
}

int main(int argc, char *argv[])
{
Foo foo;
Bar bar;
DoWork(foo);
DoWork(bar);
}

К сожалению почему-то подсветка синтаксиса в комментариях не заводится или её нет в предпросмотре.
НЛО прилетело и опубликовало эту надпись здесь
Я считаю, что сначала требуется знать шаблоны проектирования. Программист знающий шаблоны проектирования сразу определится с реализацией и в итоге не будет кучи промежуточных вариантов, которые опять же требуют время.

В конечном итоге меня очень сильно смущает смешивание сущностей. Введение объект CodeGeneratorBadProcessor, в итоге мы получаем некоторую путаницу. Да и судя по вашему примеру если этого класса не будет, то никто его и не создаст, я бы еще понял, если созданием объектов занималась фабрика. Тут все же лучше использовать исключения, а не некий объект, который выделяется из общего списка.

Опять же очень плохой момент в абстрактном классе невиртуальный деструктор, очень долго можно веселиться с ним.

Опять же путаница в стилях именования. Классы все буквы с большой, методы первые буквы с маленькой. Хорошо, что листинги еще короткие, а вот в больших листингах и реальной работе будет колоссальная путаница в итоге.

Исходный вариант тоже довольно веселый, если из enum убрать PHP, можно убрать и кидание исключения, компилятор просто не даст скомпилится коду с неправильным типом.
Про природу промежуточных вариантов я уже пояснил.

От CodeGeneratorBadProcessor мы в конце концов счастливо отказываемся. Он ни чем не хуже и не лучше изначального _language.

Про деструктор: долго веселиться можно с публичным деструктором. Попробуйте повеселиться с защищённым ,-) А у меня именно такой.

Что касается фабрик, статического полиморфизма и прочего… всё это хорошо и правильно, но на чём-то надо было остановиться :-) Что-то я стал подумывать, а не написать ли про статический полиморфизм?
Вопрос не относящийся к топику, это только у меня некоторые комментарии раздваиваются? Или у кого-то тоже?
Раздваиваются? Что Вы имеете в виду, если можно со скриншотом. По ходу только у Вас.
вот скриншоты раз и два. Браузер Opera 11.00 (сборка 1156). Если страницу открыть в другой вкладке, то раздвоения нет. Если просто обновить, то раздвоение есть. Наверное двойные записи появляются после нажатия кнопки обновления комментариев
Насчет обновить, обманул, после него все нормально. Видать кнопка обновления комментариев где-то сбоит
НЛО прилетело и опубликовало эту надпись здесь
1) в топике не рассмотрен вопрос, на который написан топик:
как выбрать динамически тип в «безIFовом программировании»?
и почему пользователь должен знать о всех типах генераторов, если ему достаточно знать номер генератора?

2) тут без фабричного метода не обойтись.
3) с отсутствием виртуального деструктора хорошо справляется boost::shared_ptr www.boost.org/doc/libs/1_45_0/libs/smart_ptr/sp_techniques.html#abstract
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
generateCode и его напарник параметров не принимает, в чём смысл городить интерфейсы?

Код ниже туп, прост, при этом легко изменяется. Понадобятся параметры, сделаем tuple<...> makejava(int param) { return make_tuple(...); }. Затем можно и интерфейс выделить.

java = make_tuple("Java...", "http...");
php = make_tuple("PHP...", "http...");

void gen(tuple<std::string, std::string> const & lang)
{
// ...
}

int main()
{
gen(java);
}

И конечно, такой коммент не заслуживает плюсов. Слишком простое и ясное решение.
а я плюсанул…
Если нету подгрузки нужных кодогенераторов в рантайме, то имхо лучше привести всё к такому виду
CodeGenerator cg;
//или же
CodeGenerator cg;
gc.generate();

Парсер лох(((
CodeGenerator<Java> cg;
//или же
CodeGenerator cg;
gc.generate<Java>();


* This source code was highlighted with Source Code Highlighter.

Вообще тут 4 основных варианта в итоге, по нарастанию сложности подсистемы:
1) Простая иерархия наследования с базовым классом, когда вызывающий код создаёт класс конкретной реализации (как в топике)
2) Подход с дженериками, который по сути является мостом
3) Подход с адаптерами
4) Подход с фабрикой (из топика cd-riper на ЖЖ)

И часто все эти подходы сваливаются в одну кучу, т.к. одну и ту же задачу можно решить каждым из них.
Попробуем разобраться с конца:

Подход с фабрикой нужен для наиболее сложной подсистемы, когда мы имеем независимые действия и классы.
Если более кратко — запрашивающий объект код знает только о необходимом действии, но не знает о требующемся ему классе.
Это позволяет инкапсулировать логику создания объекта в зависимости от параметров действия и настроек среды, используется когда такая логика нетривиальна.

Берем фабрику и накладываем условие связи каждого запрошенного действия с конкретным классом, получаем адаптер, теперь запрашивающий код уже знает конкретный тип адаптера и создаёт его.
Накладываем ещё одно ограничение, теперь код всех наших адаптеров перестаёт зависить от адаптируемой реализации: выносим весь этот код в 1 класс и получаем мост
Если накладываем ещё одно ограничение, когда классы интерфейса моста становятся тривиальными и просто перебрасывают вызовы — мы выкидываем этот класс и получаем простую иерархию наследования.

Если в заданном примере требуется создать генератор кода исходя из конфигурационного объекта, в котором содержится описание типа: «требователен к производительности, архитектура x86_64, операционная система: xxx», тогда получаем фабрику в чистом виде.

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

Если упростить всё до одинакового интерфейса кодогенерирующих классов (как в примере — генерируются только заранее известные куски кода) — получаем реализацию с мостом.

Если ещё пряглядеться к задаче, то можно увидеть, что запрашиваемая вызывающим кодом задача четко соответствует интерфейсу классов — генераторов, так что и мост нам не нужен.

Ну и не стоит забывать, что создание объектов, в зависимости от ситуации, можно упаковать в IoC контейнер.

Поэтому первым вопросом, который надо задать при написании подобного кода будет вопрос о перспективах развития подсистемы, исходя из этого уже и выбираем паттерн реализации.
В описанном примере я лично считаю подход из топика достаточно уместным, а подход из ЖЖ cd-riper'a соответственно неоправданным монструозным усложнением системы.
а чем плох такой метод
// Lang.h
class Lang {
public:
  virtual std::string generate(void) = 0;
  bool isRegistred;
}
// LangCPP.h
class LangCPP: public Lang
{
public:
  std::string generate(void){ if(isRegistred) return «i am cpp»; else return «exception»; }
}
//LangCPP.h
isRegistred = Generator::Instance().register(«cpp», LangCPP);
// LangJava.h
class LangJava: public Lang
{
public:
  std::string generate(void){ if(isRegistred) return «i am java»; else return «exception»; }
}
//LangCPP.cpp
isRegistred = Generator::Instance().register(«java», LangJava);

// Generator.h
class Generator
{
  typedef std::map<std::string,Lang> Langs;
  Langs langs;
public:
  static Generator& Instance();
  bool register(char* name, Lang &lang)
  {
    std::string strName = std::string(name);
    return langs.instert(Langs::value_type(strName, lang)).second;
  }
  
  Lang* generate(const char* type)
  {
    Langs::iterator i = langs.find(std::string(type));
    if(i != langs.end())
    {
      Lang l = (*i).second;
      return l();
    }
    
    return NULL;
  }
}

// main.cpp
std::cout << Generator::Instance().generate(«cpp»).generate();

* This source code was highlighted with Source Code Highlighter.
ппц и убожество
похоже это AbstractFactory, её какрас придумали для избавления от switch-hell кода
обычно когда вспоминают о без ифовом программировании, в качестве альтернативы указывают статическую типизацию на шаблонах… и последний вариант на нее отличненько ложится, так как все известно в компайл тайме…
Весь пост ждал реализации фабрики… безуспешно. =(
if не являются «вселенским злом», в конце концов, совсем без них программу не напишешь (если только не самую тривиальную). Но то, что они имеют тенденцию разрастаться как снежный ком и усложнять логику программы — это аксиома. Поэтому необходимо самым пристальным образом следить за ними. Самый простой способ (скажем так, первый шаг к победе) — это разбиение вложенных if, поскольку вложенные if уже начинают чуток попахивать вселенским злом.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории