Несколько вопросов по ООП в Delphi

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Chrome~, 25 Oct 2010.

  1. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    937
    Likes Received:
    162
    Reputations:
    27
    Привет!
    У меня несколько вопросов по ООП в Delphi:

    1) Итак, есть у нас следующая ситуация:
    Code:
    type
      a = class
      private
        { Private declarations }
        FNumber: Integer;
      public
        { Public declarations }
      end;
    
      b = class
      private
        { Private declarations }
        my_class: a;
      public
        { Public declarations }
        procedure SetNumber(N: Integer);
      end;
    
    implementation
    
    { b }
    
    procedure b.SetNumber(N: Integer);
    begin
      my_class.FNumber := N;
    end;
    
    Здесь ясно видно, что наша процедура SetNumber присваивает новое значение приватному полю объекта класса A. В принципе все норм. Но данная конструкция
    Code:
    procedure b.SetNumber(N: Integer);
    begin
      my_class.FNumber := N;
    end;
    работает, если класс A и B объявлены в одном юните.
    Как можно добиться того же эффекта, что и процедура SetNumber, но если классы находятся в разных модулях? Переменная FNumber должна оставаться в разделе private.
    2) Объясните мне, пожалуйста, директиву virtual (некоторые трудности в понимании ее назначения).
    3) В каких случаях вы посоветуете создавать наследники для своих классов? Когда это удобно?

    Заранее благодарен за ответы по теме.
     
  2. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    1)
    Варианты:
    1.а Сделать свойство Number ссылающееся на FNumber (в классе а)
    2.б объявить FNumber как protected-член а класс b наследником класса a
    2)
    virtual - директива указывающая что метод виртуальный и может переопределяться наследниками
    например, в delphi 2010 (может и раньше) у базового класса TObject есть такой виртуальный метод:
    PHP:
    function ToStringstringvirtual;
    создаем наследника и переопределяем его:
    PHP:
    type
     TMyClass 
    = class
     public
      function 
    ToStringstringoverride;
     
    end;

    function 
    TMyClass.ToStringstring;
    begin
      Result 
    := 'ololo';
    end;
    и используем:
    PHP:
    procedure TForm1.Button1Click(SenderTObject);
    var
     
    objTObject;
    begin
     obj 
    := TObject.Create;
     
    ShowMessage(obj.ToString); // тут показывает "TObject"
     
    obj.Free;
     
    obj := TMyClass.Create;
     
    ShowMessage(obj.ToString);// а тут уже "ololo"
     
    obj.Free;
    end;
    несмотря на то что obj обявлен как TObject на самом деле obj.ToString дает разные результаты внутри одной подпрограммы из-за того что мы применили разные конструкторы и метод виртуальный
    3)
    Например: я пишу сетовое приложение. Для выполнения действий на сайте я создаю некий базовый класс-клиент именно под сайт реализующий общую функциональность (например авторизацию) и определяющий абстрактный виртуальный метод DoWork. А от него наследуют конкретные реализации для спама, инвайта, флуда, чека акков и т.д. реализующие каждый по-своему метод DoWork. При этом клиенты объявляются как client: TBaseClass а конструктор подставляется уже "конкретного" класса. Таким образом чтобы добавить или изменить функционал мне достаточно создать нового наследника и переопределить методы, не боясь что при этом что-т осломается в проекте. Архитетура проста и понятна. Профит!

    ЗЫ Вопрос не тривиален, если останутся вопросы стучи в аську.
    ЗЫЫ Добро пожаловать в ооп =)
     
    3 people like this.
  3. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    937
    Likes Received:
    162
    Reputations:
    27
    Спасибо большое! Все понятно.
    Я просто пишу некоторый класс сейчас и хотел узнать эти аспекты, чтобы потом ничего не нужно было переписывать.

    Еще один заключительных вопрос по методах с директивой virtual и без:

    1)
    PHP:
    type
      TObject 
    = class
      public
        function 
    ToStringstringvirtual;
      
    end;

      
    TMyClass = class
      public
        function 
    ToStringstringoverride;
      
    end;
    2)
    PHP:
    type
      TObject 
    = class
      public
        function 
    ToStringstringvirtual;
      
    end;

      
    TMyClass = class
      public
        function 
    ToStringstring;
      
    end;
    3)
    PHP:
    type
      TObject 
    = class
      public
        function 
    ToStringstring;
      
    end;

      
    TMyClass = class
      public
        function 
    ToStringstring;
      
    end;
    Где здесь будет переопределена функция ToString класса TMyClass?
     
  4. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    В первом варианте.
    Во втором случае полиморфизм не работает. Какой именно метод будет вызван зависит от того как объявлена переменная. В примере 2 поста если убрать override то будет вызван метод TObject.ToString и вернет "TMyClass" вместо "ololo". Объявляя в наследнике метод с одинаковой сигнатурой виртуального метода базового класса ты просто прерываешь цепочку переопределения ToString, а сделав как в первом варианте ты можешь наследовать от TMyClass и переопределять ToString далее.
    В третьем случае метод вообще статический и опять же тут будет зависеть от того как объявлена переменная-экземпляр.

    Вообще, отличный пример наследования и полиморфизма - класс TThread
    Имеется абстрактный виртуальный метод Execute который вызывается после запуска потока несмотря на то что он не реализован в RTL, а реализует его уже программист в наследниках TThread.
    Например, можно делать так:
    PHP:
    var threadTThread;
    ..... 
    thread := TMythread.Create(False); // в классе TMythread реализован Execute
     
  5. Chrome~

    Chrome~ Elder - Старейшина

    Joined:
    13 Dec 2008
    Messages:
    937
    Likes Received:
    162
    Reputations:
    27
    Спасибо, все понятно.
    Но в процессе работы еще один вопрос появился.
    В моем классе храниться массив некоторых структур, в которых часть информации должна быть доступна для пользователя, а часть - только для самого класса.

    У меня два варианта реализации есть, какой из них лучше?

    1) Массив классов с приват и паблик полями.
    2) 2 массива записей (record), первый - с паблик данными и будет он находиться в секции public, а второй - с приват данными в секции private. Естественно, каждому элементу одного массива должен соответствовать элемент с таким же номером с другого массива.

    Лично я склоняюсь больше к первому варианту, так он более удобнее. Но в этом случае можно взять любой элемент из массива и выполнить ему метод Free из программы, которая использует класс, тогда данные потеряются. По идеи я этого делать не буду, но все равно хотелось бы узнать, как избежать этой проблемы.
     
    #5 Chrome~, 26 Oct 2010
    Last edited: 26 Oct 2010
  6. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Самый лучший вариант - использовать современные версии Дельфи, где:
    1) Записи могут хранить любые члены как и классы: свойства(с регулируемым доступом read и write), методы
    2) Все модификаторы доступа(strict private, private, public и т.д.) работают также как и в классах
    Если сделать так - ты решишь проблему первым вариантом, но без классов, и следовательно риска потерять данные.

    Если нет такой возможности то есть способ скрыть деструктор.
    Вот допустим этот класс:
    PHP:
    type
     TMyClass 
    = class
     public
      
    destructor Destroyoverloadoverride;
     private
      
    destructor Destroy(OwnerTObject); overload;
     
    end;

    TMyClass }

    destructor TMyClass.Destroy;
    begin
     raise Exception
    .Create('Not supported operation'); // если вызван дефольный деструктор то возбуждаем исключение
    end;

    destructor TMyClass.Destroy(OwnerTObject);
    begin
     
    if Owner is TStringList then // если  владелец нужного нам типа то освобождаем объект иначе исключение
      
    inherited Destroy
     
    else
      
    raise Exception.Create('Not supported operation');
    end;
    Таким образом вызов Free для объекта будет возбуждать исключение, а "настоящий" деструктор скрыт в секции private и виден внутри одного модуля, принимает аргумент объект-владелец (в данном случаем TStringList для примера)
    PHP:
    procedure TForm1.Button1Click(SenderTObject);
    var
     
    objTMyClass;
    begin
     obj 
    := TMyClass.Create;
     
    obj.Free// исключение
     
    obj.Destroy(TStringList.Create); // освобождение. класс TMyClass объявлен в этом же модуле по-этому настоящий деструктор виден
    end;
    Но это уже напоминает костыли. Самое лучшее решения имхо - использовать продвинутые записи.
    Второй вариант вообще не рассмативался ибо некрасиво как-то.
     
  7. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Вот эт да :D
     
  8. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
  9. Jingo Bo

    Jingo Bo Member

    Joined:
    25 Oct 2009
    Messages:
    368
    Likes Received:
    51
    Reputations:
    7
    Omg, не знал, казалось сгенерировать исключение, фраза что то улыбнула)))