Изучение ООП

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Изучение ООП

Сообщение Александр Яшин » 14.04.2010 10:08:43

В настоящее время разбираюсь с ООП по книге М. Кэнту "Delphi 7: Для профессионалов".
Признаюсь честно, не все мне понятно. В частности, в качестве примера, сделал программу, в которой при нажатии на кнопку динамически создается новая форма. Код:
Код: Выделить всё
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;
  Form2: TForm;
implementation

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  Btn: TButton;
  Lbl: TLabel;
  EdText: TEdit;
begin
  if not Assigned(Form2) then
    Form2:= TForm.Create(Application);
  with Form2 do
  begin
    BorderStyle:= bsDialog;
    Height:= 84;
    Width:= 324;
    Left:= 350;
    Top:= 250;
    Btn:= TButton.Create(Self);
    with Btn do
    begin
      Parent:= Form2;
      Left:= 131;
      Top:= 44;
      Caption:= 'Найти';
    end;
    Lbl:=TLabel.Create(Self);
    with Lbl do
    begin
      Caption:= 'Найти текст';
      Parent:= Form2;
      Left:= 27;
      Top:= 18;
    end;
    EdText:=TEdit.Create(Self);
    with EdText do
    begin
      Parent:= Form2;
      Width:= 140;
      Left:= 104;
      Top:= 11;
    end;
    Show;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if MessageDlg ('Закрыть форму?', mtConfirmation,
      [mbYes, mbNo], 0) = mrYes then
  begin
    Form2.Close;
    Form2.Free;
    Form2:= nil;
  end;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: boolean);
begin
  Form2.Free;
  Form2:= nil;
end;

initialization
  {$I unit1.lrs}

end.


Вроде все работает, но меня не покидает ощущение, что я сделал неправильно. Так сказать непрофессионально.
Уважаемые знатоки, подскажите что здесь не так?
Александр Яшин
новенький
 
Сообщения: 67
Зарегистрирован: 21.11.2009 09:31:01

Re: Изучение ООП

Сообщение А.Н. » 14.04.2010 11:21:01

Я не знаток, но переписал бы это примерно так:

Код: Выделить всё
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  private
    { private declarations }
  public
    Form2: TForm;
  end;

var
  Form1: TForm1;

implementation

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  Btn: TButton;
  Lbl: TLabel;
  EdText: TEdit;
begin
  if not Assigned(Form2) then
    Form2:= TForm.Create(Application);
  if (Form2 = nil) then exit;
  with Form2 do
  begin
    BorderStyle:= bsDialog;
    Height:= 84;
    Width:= 324;
    Left:= 350;
    Top:= 250;
    with TButton.Create(Form2) do
    begin
      Parent:= Form2;
      Left:= 131;
      Top:= 44;
      Caption:= 'Найти';
    end;

    with TLabel.Create(Form2) do
    begin
      Caption:= 'Найти текст';
      Parent:= Form2;
      Left:= 27;
      Top:= 18;
    end;
    with TEdit.Create(Form2) do
    begin
      Parent:= Form2;
      Width:= 140;
      Left:= 104;
      Top:= 11;
    end;
    Show;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if ((Form2 <> nil) and (MessageDlg ('Закрыть форму?', mtConfirmation,
      [mbYes, mbNo], 0) = mrYes)) then
  begin
    Form2.Close;
    FreeAndNil(Form2);
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  FreeAndNil(Form2);
end;

initialization
  {$I unit1.lrs}

end.


Добавлено спустя 3 минуты 52 секунды:
Во, всё поправил. Блин, даже тут я умудрился ошибиться. :(
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Изучение ООП

Сообщение Vadim » 14.04.2010 11:29:24

Александр Яшин писал(а):Вроде все работает, но меня не покидает ощущение, что я сделал неправильно.

А Вы попробуйте конкретизировать свои ощущения. ;)
Vadim
долгожитель
 
Сообщения: 4112
Зарегистрирован: 05.10.2006 08:52:59
Откуда: Красноярск

Re: Изучение ООП

Сообщение А.Н. » 14.04.2010 11:30:29

Кстати, как вариант:
Application.CreateForm(TForm, Form2);

И формой (в том числе уничтожением) будет управлять объект Application.
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Изучение ООП

Сообщение Climber » 14.04.2010 11:39:48

А.Н. писал(а):Кстати, как вариант:
Application.CreateForm(TForm, Form2);
И формой (в том числе уничтожением) будет управлять объект Application.

У автора в коде стоит
Form2:= TForm.Create(Application);
Это то же самое по сути. Владельцем формы будет Application в обоих случаях.
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Изучение ООП

Сообщение Александр Яшин » 14.04.2010 11:45:50

Ну, ощущение неуверенности (пока!), что все ли делаю правильно.
Вариант, предложенный А.Н. тоже работает. Но мне надо, чтобы код (мой и А.Н.) был разобран профессионалом. Хотелось бы что-то типа "вот здесь надо так, а здесь так. А вот это неправильно", ну и т.д. И вообще, какой тогда код более правильный (профессиональней что-ли)?
А.Н. писал(а):Кстати, как вариант:
Application.CreateForm(TForm, Form2);


В упомянутой мною книге применяются оба варианта и, насколько я понял, между ними не делается никаких различий.
Александр Яшин
новенький
 
Сообщения: 67
Зарегистрирован: 21.11.2009 09:31:01

Re: Изучение ООП

Сообщение А.Н. » 14.04.2010 12:00:34

2Climber:
О, блин, точно. Я-то думал почему он у меня SIGSEGV выкинул, если в OnDestroy уничтожение делать.
Тогда так:
Код: Выделить всё
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
  public
    Form2: TForm;
  end;
var
  Form1: TForm1;

implementation

{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
var
  Btn: TButton;
  Lbl: TLabel;
  EdText: TEdit;
begin
  if not Assigned(Form2) then
    Form2:= TForm.Create(Form1);
  if (Form2 = nil) then exit;
  with Form2 do
  begin

...
Код: Выделить всё
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(Form2);
end;


Но, помимо этого, Application.CreateForm ещё:
1.) Добавляет форму в список: FFormList.Add(AForm);
Думаю, что при простом создании формы, она в список не добавляется. Не знаю точно.
2.) Корректно отображает форму со стилем fsSplash.
3.) Устанавливает главную форму (что, впрочем, тут не важно).

Есть свои плюсы... Хотя, больше лишних проверок.

Александр Яшин писал(а):Хотелось бы что-то типа "вот здесь надо так, а здесь так. А вот это неправильно", ну и т.д. И вообще, какой тогда код более правильный (профессиональней что-ли)?

Тогда вам нужно к профессионалам. ;) Профессионал - это тот, кто деньги получает за работу.
Профессионалам
по всяким каналам
То много, то мало -
на банковский счёт,
А наши ребята
(за ту же зарплату)
Уже пятикратно
выходят вперёд!
;)
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Изучение ООП

Сообщение Александр Яшин » 14.04.2010 12:06:08

Ну, хорошо, не профессионалы, а знатоки!
Александр Яшин
новенький
 
Сообщения: 67
Зарегистрирован: 21.11.2009 09:31:01

Re: Изучение ООП

Сообщение Sergei I. Gorelkin » 14.04.2010 13:34:03

Delphi, равно как и Lazarus - инструмент для быстрой разработки приложений (RAD). Это известная фраза, но означает она то, что эти среды навязывают определенный стиль кода, который соотносится и с ООП, и с правильным дизайном довольно-таки опосредованно.
Чего стоит одно помещение почти всего пользовательского кода в классы, наследуемые от графических объектов (TForm, ага).
Другой очень показательный пример - Application.CreateForm. Смысл этой процедуры в том, чтобы переменная, передаваемая вторым параметром, инициализировалась до вызова конструктора, и из-за этого ее можно было использовать в обработчике OnCreate и писать в стиле M$ Visual Basic (который был популярен в период появления Delphi):
Код: Выделить всё
procedure TForm1.OnCreate(Sender: TObject);
begin
  Form1.Width := 200;
  ...


Но самая серьезная ошибка данного примера в том, что контролы на второй форме создаются, имея owner=Form1 (т.к. Self внутри конструкции with Form2 не меняет своего смысла и остается равным Form1). Из-за чего при уничтожении Form2 где угодно раньше деструктора Form1 свойства Btn.Parent и подобные остаются ссылками в никуда.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Изучение ООП

Сообщение Александр Яшин » 14.04.2010 13:47:24

Хорошо, но тогда, если можно, не покажете правильное решение?
Александр Яшин
новенький
 
Сообщения: 67
Зарегистрирован: 21.11.2009 09:31:01

Re: Изучение ООП

Сообщение А.Н. » 14.04.2010 16:05:47

Sergei I. Gorelkin писал(а):Чего стоит одно помещение почти всего пользовательского кода в классы, наследуемые от графических объектов (TForm, ага).

Ну, не столь это и плохо...

Другой очень показательный пример - Application.CreateForm. Смысл этой процедуры в том, чтобы переменная, передаваемая вторым параметром, инициализировалась до вызова конструктора, и из-за этого ее можно было использовать в обработчике OnCreate

Ёпрст!
Сколько раз видел, но ни разу внимания не обратил:
Код: Выделить всё
  // Allocate the instance, without calling the constructor
  Instance := TComponent(InstanceClass.NewInstance);
  // set the Reference before the constructor is called, so that
  // events and constructors can refer to it
  TComponent(Reference) := Instance;

Блин, ведь действительно, почему-то не приходил мне в голову вопрос о том как в Delphi-подобных, икк, средах работает OnCreate. Ведь он вызывается в конце конструктора.
Т.е. получается, что обращение в OnCreate к какому-либо компоненту формы, созданной с помощью TForm.Create() может вызвать ошибку, поскольку Self не был инициализирован? Или только непосредственно обращение к Self?

Кстати, странно:
Код: Выделить всё
constructor TCustomForm.CreateNew(AOwner: TComponent; Num : Integer);
begin
...
  FloatingDockSiteClass := TWinControlClass(ClassType);
  // Вот это - странно.
  Screen.AddForm(Self);
  EndFormUpdate; 

Ведь Self инициализируется только после того, как конструктор отработал? Или в начале, после выделения памяти?
Честно, копаться лениво... Но ведь, по идее, сейчас мне кажется, что OnCreate должен работать в любом случае...
Вроде бы, после окончания работы конструктора инициализируется только левая переменная.
Или вы имели ввиду обращение в OnCreate, типа Form1.Control?

писать в стиле M$ Visual Basic (который был популярен в период появления Delphi)

А что за стиль "visual basic'а"? Я на нём (знал когда-то только QBasic :lol: ) никогда не писал, потому не представляю.

Но самая серьезная ошибка данного примера в том, что контролы на второй форме создаются, имея owner=Form1 (т.к. Self внутри конструкции with Form2 не меняет своего смысла и остается равным Form1). Из-за чего при уничтожении Form2 где угодно раньше деструктора Form1 свойства Btn.Parent и подобные остаются ссылками в никуда.

Может, наоборот, при уничтожении Form1 раньше, чем Form2? %-)

Александр Яшин писал(а):Хорошо, но тогда, если можно, не покажете правильное решение?

См. 2-й вариант переписанного примера.
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Изучение ООП

Сообщение Sergei I. Gorelkin » 14.04.2010 17:07:33

А.Н. писал(а):Т.е. получается, что обращение в OnCreate к какому-либо компоненту формы, созданной с помощью TForm.Create() может вызвать ошибку, поскольку Self не был инициализирован? Или только непосредственно обращение к Self?

C self все нормально, он инициализирован до начала работы любого пользовательского кода. Речь о глобальных переменных вида Form1.
А.Н. писал(а):Или вы имели ввиду обращение в OnCreate, типа Form1.Control?

Именно это.
В VB тех времен не было наследования как такового, поэтому создавалась просто форма, ей можно было назначить обработчик события типа OnCreate, а в этом обработчике обратиться к объекту формы можно было только путем использования глобальных переменных вида Form1.
Из той же оперы сам факт наличия у форм событий OnCreate, OnDestroy, OnShow и подобных. Когда пользовательские классы форм наследуются от TForm, эти события нафиг не сдались, достаточно перекрыть нужные виртуальные методы.

А.Н. писал(а):Может, наоборот, при уничтожении Form1 раньше, чем Form2? %-)

Нет, при уничтожении Form1 первой картина будет другая. Form1 уничтожит принадлежащие (посредством owner) ей контролы, визуально (посредством Parent) находящиеся на Form2, и Form2 останется пустой. Хотя возможно, что у Form2 опять же останутся невалидные ссылки на эти контролы.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1405
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Изучение ООП

Сообщение AbakAngelSoft » 14.04.2010 17:15:17

А.Н. писал(а):Ну, не столь это и плохо...

Это просто отвратительно! Когда бизнес логика приложения погребена в Button1Click - ах! Как его поддерживать? как развивать?
А.Н. писал(а):Т.е. получается, что обращение в OnCreate к какому-либо компоненту формы, созданной с помощью TForm.Create() может вызвать ошибку

К Self сразу за NewInstance можно обращаться безболезненно а вот к глобальным переменным типа
Код: Выделить всё
var Form1: TForm
без инициализации этой переменной обращаться нельзя!
А.Н. писал(а):А что за стиль "visual basic'а"

Работа с глобальными переменными Form1 вместо нормального Self

Добавлено спустя 36 секунд:
Sergei I. Gorelkin
Опередили :)
Аватара пользователя
AbakAngelSoft
постоялец
 
Сообщения: 273
Зарегистрирован: 06.08.2008 19:28:26
Откуда: Краснодар

Re: Изучение ООП

Сообщение А.Н. » 14.04.2010 17:57:15

Sergei I. Gorelkin писал(а):В VB тех времен не было наследования как такового, поэтому создавалась просто форма, ей можно было назначить обработчик события типа OnCreate, а в этом обработчике обратиться к объекту формы можно было только путем использования глобальных переменных вида Form1.

А как же была иерархия классов организована? Или там такого не было? А визуальные контролы?

Из той же оперы сам факт наличия у форм событий OnCreate, OnDestroy, OnShow и подобных. Когда пользовательские классы форм наследуются от TForm, эти события нафиг не сдались, достаточно перекрыть нужные виртуальные методы.

С одной стороны, конечно да... Я тоже, вроде, задумывался (хм... что-то я за собой такого... :? ) зачем нужно OnCreate, когда возможно перекрыть конструктор...
Но, с другой стороны, самая идея событий весьма хорошая. Хотя, конечно тоже... :-\

Нет, при уничтожении Form1 первой картина будет другая. Form1 уничтожит принадлежащие (посредством owner) ей контролы, визуально (посредством Parent) находящиеся на Form2, и Form2 останется пустой. Хотя возможно, что у Form2 опять же останутся невалидные ссылки на эти контролы.

Ааа... Понял. Она не будет пытаться их уничтожить? Parent отвечает только за то, где контролы отображаются?

AbakAngelSoft писал(а):Это просто отвратительно! Когда бизнес логика приложения погребена в Button1Click - ах!

"Бизнес-логика", в RAD? :D По-моему, всё-таки, RAD - больше подходит для проектирования интерфейсов.
Форма - элемент интерфейса. И вполне логично, что она наследуется от графических элементов.
Ну а код, уже пользователь сам решает, куда ему загнать... Не так?

Как его поддерживать? как развивать?

Хреново, как же ещё? Или, вообще, - никак. :( Вначале бы написать... :-\
У меня такое подозрение, что если нужно писать что-то сложнее простой утилиты, нужно бизнес-логику выносить в отдельный "слой", иначе потом не обобраться проблем. :(
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Изучение ООП

Сообщение AbakAngelSoft » 15.04.2010 09:13:13

А.Н. писал(а):Ну а код, уже пользователь сам решает, куда ему загнать... Не так?

Код - коду рознь и должен код лежать по своим местам!
А.Н. писал(а):У меня такое подозрение, что если нужно писать что-то сложнее простой утилиты, нужно бизнес-логику выносить в отдельный "слой", иначе потом не обобраться проблем

Правильное подозрение.
Мне как-то предлагали чуть-чуть доделать программу где код был написани именно так - весь в обработчиках событий. Каждый обработчик по три сотни строк.
После недели анализа я вернул заказчику предоплату и предложил написать программу заново.
PS. Автор от поддержки своего "творения" отказался еще раньше.

Добавлено спустя 2 минуты 2 секунды:
Кстати в описанном коде встречались замечательные комментарии перед каждым обработчиком:
Код: Выделить всё
// Нажатие кнопки "Сохранить"
// Нажатие кнопки "Отменить"
// Закрытие главной формы

Это порадовало больше всего!
Аватара пользователя
AbakAngelSoft
постоялец
 
Сообщения: 273
Зарегистрирован: 06.08.2008 19:28:26
Откуда: Краснодар

След.

Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 37

Рейтинг@Mail.ru