"По мотивам" этой серии я хотел бы озвучить очередной факт из "98-ми вещей, которые должен знать каждый Delphi программист" (где остальные 97 вещей взяты из указанной выше серии ;) ). Это действительно must-know, но он не покрыт в указанной серии (видимо потому, что он достаточно специфичен?).
Типичный код в "типичной" Delphi-программе
В любом случае, надо сказать, что возникает это не на пустом месте - в определённом смысле Delphi "поощряет" подобную практику. Заключается она в следующем: вы пишете весь код программы в обработчиках событий формы. Например:procedure TForm1.Button1Click(Sender: TObject); var FS: TFileStream; // ... begin FS := TFileStream.Create(Edit1.Text, fmOpenRead or fmShareDenyWrite); try ... // какая-то обработка файла из Edit1 finally FreeAndNil(FS); end; end;Зачастую вообще весь код программы находится в обработчиках событий.
Что с этим не так?
Ну, что не так с этим подходом? Ведь когда ты щёлкаешь по кнопке на форме, Delphi услужливо генерирует и ассоциирует обработчик события, открывает редактор, позиционирует курсор на begin/end, как бы приглашая вас к написанию тут логики программы.Давайте считать:
- Ваш код зависит от формы. Нужна ли вам форма, чтобы обработать файл? Нет. Форма служит только для ввода параметров и запуска процесса работы. Отсюда следует несколько следующих пунктов.
- Если к вам подойдёт начальник и скажет: "слушай, тут заказчик попросил сделать консольный вариант твоей программки - они хотят запускать её в своих скриптах автоматизации", то скажете ли вы ему, что это потребует написание программы с нуля? "Ну у тебя же всё готово?" Да, готово. Только совершенно не предназначено для таких изменений.
- Потом к вам снова подойдёт начальник и скажет: "слушай, нашего заказчика уже достало, что после каждого изменения в твоей программе что-то плывёт. Давай-ка ты напишешь тестики, которые закрепят основной функционал, ага?" Уммм... вот только чтобы запустить тест вам придётся создать форму, ввести данные в контролы на форме, щёлкнуть по кнопке... и ещё как-то распознать результат с формы!
- И снова к вам подходит начальник и говорит: "слушай, может я тебя и достал, но у заказчика многопроцессорная машина, и они хотят, чтобы можно было открыть несколько окон с обработкой - пусть машина себе молотит по четыре файла сразу". Упс. Вот только у вас всё завязано на форму, которая только одна, да и вообще вы использовали глобальные переменные.
Собственно, проблема с вышеуказанным подходом в том, что логика вашей программы тесно связана с её интерфейсом пользователя. Они так переплетаются вместе, что просто неразделимы. Может, в примере выше это не очень заметно, но в больших программах, построенных по такому принципу это просто очевидно.
Нет, подобный подход отлично работает в программке из 10 строк, которую вы написали, чтобы проверить идею, но совершенно неприменим в реальных проектах. К сожалению, многие Delphi-программисты в отсутствии какого-либо образования (самостоятельного или же в учебном заведении) просто не подозревают, что можно делать как-то по-другому! Я пишу сейчас эти строки, чтобы в следующий раз кинуть в кого-нибудь ссылкой :D
Что с этим делать?
Ну, вероятно, существует несколько подходов к этой проблеме. В любом случае, их суть сводится к тому, что вам нужно чётко разграничить интерфейс пользователя (то, с чем он работает, будь это формочки, командная строка или web-страница) и логику программы (её функционал, то, что она делает, рабочую часть).Что значит "разграничить"? В простейшем случае это означает выполнение правила "код логики программы не должен ссылаться на интерфейс пользователя". К примеру, в нашем коде в начале поста рабочий код ссылается на Edit1 - ведь там лежит имя файла. Так вот, такого быть не должно: а вдруг вам имя файла передают параметром в командной строке? А вдруг его задаёт модульный тест?
Как это можно сделать? Ну, подумайте сами: если вы не можете ссылаться на интерфейс пользователя, то данные, приходящие с этого интерфейса, нам должен кто-то передавать. Оставим пока этого "кто-то": куда он должен передавать данные? Если вы практикуете процедурный подход, то, наверное, он должен передавать вам данные в параметрах. Если вы практикуете ООП, то к указанному способу добавляется ещё два: данные могут передаваться в конструктор или через свойства объекта. Окей, если вы сейчас читаете этот пост, то навряд ли вы вообще практикуете эти подходы - скорее вы придерживаетесь подхода "пиши весь код в Button1Click" ;) Оставим пока в стороне покрытый мхом процедурный подход - обратимся к ООП.
ООП говорит нам, что у нас должен быть объект, который будет заключать в себе логику программы. Объект, заключающий в себе интерфейс пользователя, у нас уже есть - это наша форма TForm1. Тогда создаём новый модуль File/New/Unit и начинаем писать новый объект, имя которого, за неимением пока лучших вариантов, пусть будет TWorker (ну или TИмяВашейПрограммыWorker):
unit Unit2; interface type TWorker = class end; implementation end.Зачем нужен отдельный модуль: почему бы не пихнуть класс в Unit1? В-принципе, можно. Но лучше так не делать. Вынос рабочего кода в отдельный модуль имеет одно существенное преимущество (оно, впрочем, не единственное): форсирование разделение кода. Иными словами, вы не должны указывать в uses модуля Unit2 свой модуль с формой (Unit1) - это автоматически обеспечит выполнение требования "рабочий код не должен зависеть от интерфейса". В противном случае (размещение нового класса прямо в модуле с формой) вы можете ошибочно использовать в рабочей логике ссылки на форму.
Далее, вы смотрите, что должна уметь делать ваша программа - и создаёте по методу на каждое действие. Давайте пока ограничимся не слишком сложной программой, которая выполняет небольшой диапазон действий. Понятно, что для больших программ вы не ограничитесь одним объектом - у вас их будет много, и имена они будут тогда иметь поосмысленнее, а не просто TWorker (например, TDocument, TDocumentPrinter, TDocumentOptions и т.п.).
Для первого приближения вполне можно взять методы, которые копируют действия, которые вы выполняли в обработчиках событий. Как этот код будет работать с данными, которые сейчас у вас задаются на форме? Ну, есть несколько вариантов:
- Вы передаёте их параметрами в метод.
- Вы передаёте их в конструктор объекта.
- Вы передаёте их через свойства объекта.
... type TWorker = class procedure ProcessFile(const AFileName: String { и другие параметры, если надо } ); end; implementation procedure TWorker.ProcessFile(const AFileName: String { ... } ); var FS: TFileStream; // ... begin FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite); try ... // какая-то обработка файла AFileName finally FreeAndNil(FS); end; end; ...Обратите внимание, что теперь этот код не связан с формой - в нём нет ссылок на Form1 и на Edit1. И вы можете вызвать его и из консольного процесса, передав имя файла, заданное в командной строке. Вы можете вызвать его из модульного теста, передав фиксированное имя тестового файла. Вы можете создать много экземпляров объектов и запустить их все одновременно. Короче, теперь вы можете делать с кодом целую кучу вещей.
Как использовать такой объект в программе? Ну, во-первых, очевидно, что надо вписать имя модуля Unit2 в uses нужного модуля :D Далее вариантов немного:
- Создать объект на всё время жизни формы.
- Создать объект только на время, в момент вызова обработчика события.
Ну, наш пример совсем прост, поэтому второй случай будет в тему:
procedure TForm1.Button1Click(Sender: TObject); var Worker: TWorker; begin Worker := TWorker.Create; try Worker.ProcessFile(Edit1.Text { и другие параметры, если надо }); finally FreeAndNil(Worker); end; end;Если же вы выберите первый способ, то FormCreate и FormDestroy, вероятно, будут хорошими кандидатами для помещения в них создания и удаления объекта:
type TForm1 = class(TForm) ... private FWorker: TWorker; end; ... procedure TForm1.FormCreate(Sender: TObject); begin FWorker := TWorker.Create; end; procedure TForm1.Button1Click(Sender: TObject); begin FWorker.ProcessFile(Edit1.Text { ... }); end; procedure TForm1.Button2Click(Sender: TObject); begin FWorker.КакойТоДругойМетод({ ... }); end; procedure TForm1.FormDestroy(Sender: TObject); begin FreeAndNil(FWorker); end;К сожалению, у меня нет возможности дать развёрнутый пример большой и запутанной программы, где преимущества подобного подхода были бы очевидны. В частности, в примере выше может быть не ясно:
- Чем объект лучше простой процедуры ProcessFile?
- Чем метод ProcessFile в TWorker лучше метода ProcessFile в TForm1?
- И т.д....
Какой код должна выполнять форма?
Разумеется, не весь код из обработчиков событий нужно выносить в рабочие объекты. Какой код нужно оставить? В форме должен остаться код, который манипулирует самой формой (в общем случае: UI вообще) и вызывает методы рабочих объектов. Например:procedure TForm1.Button1Click(Sender: TObject); begin Button1.Enabled := False; // не должно находится в TWorker, // т.к. это user interface, а не логика программы try FWorker.ProcessFile(Edit1.Text { ... }); finally Button1.Enabled := True; end; end;
На самом деле...
А на самом деле я рекомендую вам почитать банду четырёх о шаблонах проектирования. Я действительно рекомендую это сделать, потому что эта заметка - очень краткое и упрощённое изложение, чья цель - дать начальное представление, а не служить мануалом. В частности, я ничего не сказал, что делать, если вашему рабочему коду нужно взаимодействовать с пользователем (показать сообщение или спросить дополнительные данные).Читать далее: "Дружественность" в Delphi.
К сожалению, этот пост весьма актуален =)
ОтветитьУдалитьДаже выделен анти-паттерн такой http://ru.wikipedia.org/wiki/Магическая_кнопка
Спасибо за ссылку!
ОтветитьУдалитьЕще небольшое дополнение:
ОтветитьУдалитьЕсли допустим метод FWorker.BlaBla() выполняется длительное время, и пользователя надо осведомлять о процессе выполнения, то классу TWorker следовало бы назначить события, например:
OnMsg(const Msg: string);
А в форме написать обработчик события с выводом сообщения в Label, Memo, etc.
Таким образом:
1. TWorker по-прежнему ничего не знает про форму и не зависит от интерфейса. ему по барабану как его сообщение будет обработано, и будет ли обработано вообще.
2. Форма сама определяет как ей отоброжать сообщение. Достаточно изменить код обработчика.
А как лучше сделать взаимодействие с пользователем из такого класса, или допустим создание других форм. Можно пример?
ОтветитьУдалитьКак я уже сказал - это просто введение, а не руководство к действию.
ОтветитьУдалитьТем не менее, один из ответов на этот вопрос уже дал G-Host выше: заведите у TWorker событие (callback). Когда вам нужно в TWorker взаимодействовать с пользователем - вы вызываете событие.
Тот, кто будет использовать класс (будь это форма, консоль или модульный тест), назначат обработчик этого события и реализуют его по-своему. GUI приложение покажет диалоговое окно, консоль напишет приглашение к вводу информации и считает её через Readln, а модульный тест подсунет заранее готовый ответ.
Это общий пример. В частных ситуациях вы можете использовать и другие подходы (к примеру, форма реализует интерфейс, который вы передаёте в TWorker) - смотрите по обстоятельствам. Как я уже сказал: прочитайте книжку.
такой подход - первый шаг к выполнению программы в асинхронном виде. Никто не мешает сделать TWorker классом, способным выполняться в отдельном потоке, с последующим уведомлением главного потока о завершении задачи.
ОтветитьУдалитьК тому же такой подход ведет к изучению вариаций MVC, MVP, и ище с ними - там задачи каждого блока (модели, представления, контроллера, презнетера) - расписаны. К сожалению, живых и интересных библиотек или фреймвоков для дельфей не наблюдал. только в области ORM/OPF что то теплиться, а в области MVC - глухо. Видимо, разбаловала народ визуальная среда разработки ... если кто то даст ссылки на интересные фреймвоки - буду признателен.
Народ не разбаловал кто-то, а развратил подход к созданию глубоких иерархий классов. Это порок, с которым вынужден мириться любой программист на дельфи, ибо так написана VCL.
ОтветитьУдалитьТот же GoF обязателен к прочтению, ибо показывает, что глубокие иерархии не самоцель - есть еще агрегация, на основе которой можно выстроить гибкие структуры.
Поддерживаю полностью автора поста - GoF (http://www.ozon.ru/context/detail/id/2457392/) обязателен к прочтению. Придется, конечно, поболеть после прочтения болезнью вставлять в любые дыры Мосты и Посредников, но это, как я думаю, полезно.
Еще можно на библиотеку Swing из Java (я смотрел для версии Java 1.4), чтобы понять, как можно организовать гибкость GUI без многоуровневого наследования. Хотя к Swing'у у проф. разработчиков немало претензий, но, я думаю, что ознакомиться будет полезно.
Могу добавить, что для выделения бизнес логики удобно собирать процедуры в классовые методы, типа:
ОтветитьУдалитьTWorker = class
class ProcessFile;
end;
Делаем вызовы:
TWorker.ProcessFile;
Отпадает необходимость создавать и чистить класс. Читаемось повышается.
Есть еще хитрый прием создания абстрактного класса с бизнес логикой в виде классовых методов. Создание его потомков и использование
нужного класса в зависимости от ситуации. Этот метод широко использует DevExpress для реализации различных видов прорисовки.
Во многих ситуаций имеет смысл делать полноценный класс, потому что вы можете инициализировать его с параметрами (в конструкторе), а также хранить промежуточные результаты или параметры в экземпляре класса - что не получится с классовыми методами.
ОтветитьУдалитьЕсли вы решитесь сделать что-то классовым методом - что если потом вам нужно будет это расширить? Разделять данные с другим методом? Вам придётся переписывать кучу кода, который вызывает метод без создания экземпляра.
Поэтому в виде классовых методов реализуются только относительно простые вещи.
Для понимания необходимости MVP надо нарваться на второй и третий подводные камни:
ОтветитьУдалить1. Хочется на одни и те же формы вешать разные модели (Workers).
2. Логику интерактивного редактирования также желательно вынести из формы в невизуальный редактор (Presenter), оставив ей лишь отображение данных и получение информации и команд от пользователя.
Фреймворк станет побочным продуктом первой же MVP-реализации крупного проекта. Собственно на работе зарождение такого и наблюдаю.
По поводу классовых методов нужно немного изменить подходы к разработке и Вы получите дополнительную гибкость. Объекты с классовыми методами можно наследовать, как любые другие. По поводу данных: они должны приниматься в виде параметров метода.
ОтветитьУдалитьПростые вещи? Все гениальное просто...
Иными словами, это означает:
ОтветитьУдалитьБыло:
type
TObj = class
FField: Integer;
procedure DoSomething;
end;
Стало:
type
TObjParams = class
FField: Integer;
end;
TObj = class
class procedure DoSomething(Params: TObjParams);
end;
Понятно, что когда параметр 1 - его и передать напрямую можно. А когда много - получается ещё больше кода, чем было.
Вероятно, такой подход будет удачно смотреться только в некоторых случаях - когда параметров мало, либо же параметры принадлежат другому объекту (так что мы можем передать этот объект за раз). Частным случаем второго варианта является случай, когда нам надо разделять один набор параметров между несколькими объектами.
Классовые методы удобны при необходимости изменить поведение объекта. Например, функции рисования объекта в скинах можно засунуть в отдельный класс и пользовать нужный. А данные, которые передтся классу - это отскиненный объект.
ОтветитьУдалитьКак только рисовальщику понадобятся собственные данные, то методы класса сделают ручкой. Всяко лучше сразу работать не с классами, а с объектами. Единственное исключение - большие коллекции, где затраты на создание-удаление объектов велики.
ОтветитьУдалить@bonart: ну - не совсем представляю, зачем рисовальщику могут потребоваться собственные данные кроме всех имеющихся у отскиненого объекта данных. Впрочем, никто же не говорил, что наличие классовых методов убивает необходимость создавать объекты. Просто можно принять к сведению, что набор логически связанных процедур и функций можно связать пучком в классе и обозвать классовыми методами - и применять по необходимости, в дополнение, а не вместо объектов.
ОтветитьУдалитьДо ВСЕХ имеющихся данных рисовальщику вообще не добраться - только до публичных.
ОтветитьУдалитьВязать пучком процедуры в псевдоклассы не нужно - слава богу не Ява. А использование класса вместо объекта во-первых хуже читается, во-вторых не дает использовать данные экземпляра, в-третьих, не имеет никаких преимуществ, кроме мизерного - минус одно создание-уничтожение объекта.
В общем, можно сойтись на том, что классовые методы в объекте-заглушке, принимающие параметром другой объект - это что-то вроде класс-хэлперов для этого второго объекта.
ОтветитьУдалитьДа, это могут быть и процедуры, но классовые методы могут быть виртуальными и наследоваться.
Я решил вступиться за классовые методы.
ОтветитьУдалитьДумаю, что у многих бывают проблемы с классификацией утилитарных функций. Вот надо тебе спец. функцию. А она ну никак не подходит целиком в какой-то класс. Безусловно, ее можно побить на несколько простых функций. Но это так удобно - одна непонятная функция используется 1000 раз в программе и экономит много строк кода. Куда ее девать? Как ее называть? Куда помещать?
У меня подход на это дело такой. Создаю класса с статическими классовыми функциями. В них реализую требуемый специфический функционал. Называю класс примерно так (это пример) - TStrUtilsEx. Кидаю туда все, что есть хоть косвенно относящиеся к строкам. В ходе работы периодически пересматриваю код. Иногда из TStrUtilsEx выделяю что-то в общие функции.
Т.е. статические классовые методы - хорошее средство для временных утилитарных функций, которые не знаешь куда деть сейчас, но надеешься, что придумаешь им место потом.
Я бы развил защиту классовых функций. Во первых, у них есть возможность стать виртуальными. Во вторых, они объединяются единым классом-это важно, например, для описания скина. То есть классовые функции не просто объединены в класс, а при этом связаны некоей общей концепцией - например, отрисовать управляющий элемент в определенном стиле. Или сюда могут подойти классы, описывающие API (directX/d2d).
ОтветитьУдалитьВажное замечание про то, почему сложно создать объект. Основная сложность при создании объектов-это определение времени его жизни и своевременное удаление. В этом и есть сложности. Поэтому классовые методы в этом случае упрощают ситуацию.
Если возникают сложности со временем жизни объекта - значит настала пора использовать заменить прямую ссылку на него интерфейсной.
ОтветитьУдалитьКласс как эрзац-пространство имен использовать не люблю - нечитабельно и нерасширяемо.
>классовые методы в объекте-заглушке, принимающие параметром другой объект - это что-то вроде класс-хэлперов для этого второго объекта.
Разве не проще передать этот другой объект один раз как параметр конструктора?
>>> Разве не проще передать этот другой объект один раз как параметр конструктора?
ОтветитьУдалитьОт ситуации зависит. Вы же не создаёте классовый хэлпер перед использованием - а просто используете его. Так же и тут.
совсем не понимаю предмета разгоревшегося спора!))
ОтветитьУдалитьесть классовые методы, которые можно использовать в определенных ситуациях. Какой смысл спорить с тем, что они не нужны? Да, решение не расширяемое в плане добавления локальных данных для класса - прийдется создавать объект. Но не во всех случаях нужно иметь расширяемое решение - чтобы не наступить на грабли переинжиниринга.
Имеет смысл разобрать конкретную ситуацию использования классовых методов и указать - какие альтернативные подходы лучше использовать и почему.
>> Имеет смысл разобрать конкретную ситуацию использования классовых методов и указать - какие альтернативные подходы лучше использовать и почему.
ОтветитьУдалитьПо мне у чисто классовых методов множество приемлемых применений пустое. Дело в том, что они, как и хелперы, вносят явные ограничения, устранение которых требует крупных переделок. Но в отличие от хелперов, методы классов нельзя использовать с немодифицируемым чужим кодом. В результате все преимущество методов классов перед объектами в отсутствии явных инициализации-финализации. Это экономит пару строк кода на стороне "клиента", но делает громоздким код "исполнителя" (инициализацию-финализацию вместо конструктора и деструктора приходится размещать прямо в методе класса). Полагаю это недостаточной компенсацией за отсутствие своих данных.
К сожалению в посте, да и в его обсуждении, практически не упоминаются интерфейсы (IInterface), а между тем это очень мощный инструмент при проектировании приложений.
ОтветитьУдалитьЛично я вообще стараюсь придерживаться следующих принципов (грубо):
1. Со всеми более или менее значимыми объектами в системе работаем только через соответствующий интерфейс.
2. Объект реализющий тот или иной интерфейс создается только посредством фабрик.
Например:
CoMyObject = class
class function Create(const AParam: IMyParam): IMyObject;
end;
3. Четко разделять бизнес-модель (т.е. объектную модель) и дополнительные функции типа аутентификации, авторизации, логирования и пр.
Таким образом для реализации сущности предметной области создаются 4 модуля:
1. untMyObjectInterface - объявление интерфейса
2. untMyObjectClass - объявление класса реализующего интерфейс
3. untMyObjectCoClass - описание фабрик (см. паттерн "Фабрика"), создающих экземпляр класса реализующего интерфейс
4. untMyObjectHelper - описание сервисов (т.е. вспомогательных процедур, классов и пр.) для работы с этим интерфейсом
Выгоды:
- Описание бизнес-модели четко отделено от ее реализации;
- При помощи фабрик легко меняем реализацию интерфейса (путем создания экземпляра другого класса);
- Весь служебный код оформлен отдельно и не "захламляет" реализацию объектной модели;
- Громадный простор для рефакторинга не только отдельных методов, но и целых классов;
- Маcштабируемость. Создаем в фабрике другой класс и вот мы уже работаем не с Oracle, а с MS SQL Server (или вообще с WEB-сервисом);
- Модульное тестирование;
- Ну и как дополнительный плюс - это сборщик мусора;
Я напомню, что этот пост написан "открыть глаза приверженцам Магической Кнопки". Вы слишком многого хотите от простой заметки.
ОтветитьУдалитьВы слишком суровы. Комментарии к посту постом не являются. Это уже дискуссия по проблеме, которая вполне может открыть "приверженцам" глаза еще шире, т.к. в одном месте смогут увидеть несколько разных подходов. От себя добавлю, то поскольку VCL - библиотека компонентов, то обозначенную проблему можно решать при помощи компонентов, при этом GUI-решениях (на основе TForm) нет необходимости описывать инициализацию объекта (ну в определенном смысле, ведь, например, при использовании таймера его конструктор не прописывается в коде, а инициализация осуществляется при помощи свойств, правда обработчик OnTimer страдает болезнью, описываемой в посте), и связывать компоненты можно будет при помощи инспектора объектов...
ОтветитьУдалитьОб этом давным давно писал дятька Рэй Конопка.
Ээээ... ну извиняюсь :)
ОтветитьУдалитьЕще неплохо бы было написать пост с описанием и сравнением существующих фреймворков ORM, DAO (Data Access Object), Repository, Аналога LINK, аспектов и пр. для Delphi.
ОтветитьУдалитьИнформации в сети крайне мало, по крайней мере на русском языке. Или я просто искать не умею :)
Эээээээ..... ну напишите.
ОтветитьУдалитьКакое я имею отношение к, скажем, ORM? Никакого.
Насколько я понимаю, исходя из темы топика и жажды знаний про ORM, можно направить в сторону MDA (есть книжка на русском!) и Bold for Delphi (на английском точно что-то есть). Была еще одна штука (попроще, чем MDA), но название я уже не помню.
ОтветитьУдалитьVCL однопоточна? Вот теперь я понимаю, почему у приверженцев одной кнопки программа виснет почти всегда, когда используется sleep...
ОтветитьУдалитьСтранный комментарий. А VCL тут при чём? Вызов Sleep в UI-потоке - это диагноз.
ОтветитьУдалитьВызов Sleep в UI-потоке - это диагноз.
ОтветитьУдалитьЭто простой способ наглядно показать, почему надо разносить код формы и полезный код. К сожалению, взят из практики.
Забавно, но стоит заменить TForm на TObject(а еще лучше - просто дописать IWorker к TForm) в объявлении формы и, после удаления ненужных полей, готов чистый TWorker. Это несколько умаляет значимость доводов о требованиях начальника.
Я не хочу ввязываться в дискуссию что хорошо а что плохо, просто размышлял на досуге над Вашим постом.
>>> Забавно, но стоит заменить TForm на TObject(а еще лучше - просто дописать IWorker к TForm) в объявлении формы и, после удаления ненужных полей, готов чистый TWorker.
ОтветитьУдалитьВ таком простом примере - да. Но это всего лишь пример.
А в реальном проекте, написанном на принципе волшебной кнопки, сделать так будет очень сложно, потому что UI и рабочий код перемешаны в нём страшнейшим образом. В особо запущенных случаях будет проще переписать код заново.
Интересно, у какого процента дельфи-программистов проект перестанет компилироваться если перенести дефолтную глобальную переменную класса формы(var Form1: TForm1) в dpr?
ОтветитьУдалитьперенести глобальную переменную формы(var Form1: TForm1) в dpr?
ОтветитьУдалитьНе могу даже представить себе, что дельфя в таком случае перестанет компилировать (Delphi7).
>>Не могу даже представить себе, что дельфя в таком случае перестанет компилировать (Delphi7).
ОтветитьУдалитьТак прикол в том что если есть ссылки на "Form1" в модулях проекта то перестанет. Эта переменная нужна для Application.CreateForm, не более. А я у многих видел обращение к главной форме из глобальных подпрограмм и других классов через эту переменную. И это как раз признак привязки к интерфейсу. Так что, это можно считать маленьким тестом архитектуры =)
Допустим я использую процедурный подход, как мне в реализации процедуры не ссылаться на объект Form1 чтобы добраться до свойств и методов объектов расположенных на форме, скажем тотже clHttp1 расположенный на форме:
ОтветитьУдалитьprocedure GetHtml(URL: string);
var
html: TStrings;
begin
html := TStringList.Create;
try
Form1.clhttp1.Get(URL, html);
finally
html.Free;
end;
end;
вот не хочется мне создавать объект динамически и настраивать его свойства при создании, есть ли выход?
И ещё вопрос, когда необходимо определять свои процедуры и функции в классе формы, а когда это запрещается?
procedure GetHtml(HTTP: TIdHTTP; const URL: string);
ОтветитьУдалитьКому надо - сам передаст. Может с формы, а может - создаст сам, динамически.
Но бред какой-то получается. В чём смысл существования процедуры GetHtml, если по своей сути она является методом к объекту, только что вынесенному отдельно в виде процедуры? Так и делать её надо методом, а не процедурой.
А так получается, что постановка задачи не корректна: вы изначально делаете "неправильное" действие (вынос метода объекта в автономную процедуру), а потом спрашиваете: а правильно-то как? А правильно-то было этого не делать.
>>> вот не хочется мне создавать объект динамически и настраивать его свойства при создании
Если вопрос про лень - то тут или-или. Если ленимся - то передаём IdHTTP извне. Если делать правильно - то делаем наследника IdHTTP или декоратор. В промежутке между этими вариантами - создание IdHTTP в GetHtml (кстати, можно же не IdHTTP, можно WinInet).
>>> И ещё вопрос, когда необходимо определять свои процедуры и функции в классе формы, а когда это запрещается?
Ответ на вопрос зависит от: кем запрещается?
Если это был вопрос про создание хорошего кода, то тут так просто не ответить. Могу посоветовать взять в руки GoF или Макконнела.
Если кратко:
- Форма должна быть единым объётом, чёрным ящиком
- Манипулирование компонентами формы должно производиться самой формой (её внутренними методами)
- Наружу должна выставлять методы по манипулированию собой
- Каждый метод должен выполнять одну задачу. Если метод выполняет две задачи, его нужно разбить на два
- Логика программы не должна содержаться в форме
Как-то так.