Ir para o conteúdo
thoga31

OOP, debate sobre a alteração de métodos herdados

Mensagens Recomendadas

thoga31

Considere-se o seguinte código:

{$mode objfpc}
program OOP_Heranca;

type TA = class(TObject)
       function func : integer;
    end;

    TB = class(TA)
       function func : integer;
    end;

    TC = class(TB) end;

function TA.func : integer;
begin
    func := 10;
end;

function TB.func : integer;
begin
    func := 20;
end;

var c : TC;

begin
    c := TC.Create;
    writeln(c.func);
    c.free();
    readln;  // Pausa
end.

O seu output é 20, e não 10. Houve uma redefinição da função func de TA para TB, mas não para TC.

Gostaria, a partir daqui, debater o que são, quando são utilizadas e qual a sua utilidade, das seguintes "etiquetas", "rótulos" ou "classificações" que se pode dar aos métodos das classes:

overload
override
inherited
virtual
abstract
deprecated

Pretendo com isto reunir informação, até para eu consolidar estes conceitos. Achei melhor que seria mais interessante assim. :)

Peguem no código que dei para explicarem cada uma destas coisas, acho que é excelente para isso - é simples e elucidativo.

Cumprimentos pascalianos e delphianos,

thoga31 ;)

Editado por thoga31

Knowledge is free! | Occasional Fortnite player

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Kline777

Desculpem vir complicar em vez de explicar mas agora deu-me um brain freeze...

Tanto quanto tinha percebido, o override servia para dizer por exemplo, que uma funcao de uma classe filha (TB.Func) estava a substituir uma funcao com o mesmo nome na classe pai (TA.func).

Ou seja, ao declarar a TB.func com '

function func : integer; override

;' é que iamos obter resultado que está a ser apresentado.

Resumindo: Se como puseste neste exemplo, ao chamares a TC.func ele sabe que é a da definiçao da TB, para que serve o override então?

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
pwseo

Kline: se numa situação hipotética tivesses um conjunto de objectos que não sabias se seriam TA, TB ou TC apontados por um apontador (lol), ao chamar func irias obter sempre o resultado de TA.func porque os métodos não seriam "resolvidos" durante a execução mas sim durante a compilação (e aí o computador assumiria TA, a classe base).

Mas pronto, cá vai uma tentativa de explicar algumas coisas de uma forma mais prática... Essencialmente tem a ver com o facto dos métodos serem estáticos (determinados na compilação) ou virtuais (dinâmicos, determinados durante a execução).

Para simular isso fiz casting de tipos de objectos no seguinte programa... Leiam com tempo e interpretem os resultados com cuidado :)

{$MODE OBJFPC}
program oop;

type
 (* Uma classe normal *)
 TParent = class (TObject)
   function method: integer;
 end;

 (* Uma subclasse típica, derivada de TParent *)
 TChild = class (TParent)
   function method: integer;
 end;

 (* Esta classe é basicamente igual à TParent mas a função 'method' foi
    declarada como 'virtual' *)
 TVirtualParent = class (TObject)
   function method: integer; virtual;
 end;

 (* A subclasse de TVirtualParent, onde declaramos 'method' como 'override' *)
 TOverriddenChild = class (TVirtualParent)
   function method: integer; override;
 end;

 (* É uma subclasse basicamente igual à anterior, mas não dissemos que 'method'
    é 'override' *)
 TNotOverriddenChild = class (TVirtualParent)
   function method: integer;
 end;

 (* Classe igual à TParent, mas com 'method' declarado como 'abstract' e
    'virtual'.
    Isto faz com que além de virtual, o método só tenha que ser implementado
    nas subclasses desta classe.

    Nota: só métodos virtuais podem ser abstractos, pelo menos no FPC. *)
 TAbstractParent = class (TObject)
   function method: integer; virtual; abstract;
 end;

 TAbstractChild = class (TAbstractParent)
   function method: integer; override;
 end;

 (* A keyword 'overload' é utilizada essencialmente para compatibilidade com o
    Delphi, uma vez que no Free Pascal todos os métodos podem ser overloaded
    por defeito sem nenhuma keyword para isso. *)
 TOverloadClass = class (TObject)
   function method: integer; overload;
   function method (v: integer): integer; overload;
 end;

 TOtherParent = class (TObject)
   procedure method; virtual;
 end;

 TInheritedClass = class (TOtherParent)
   procedure method; override;
 end;

function TParent.method: integer;
begin
 method := 10;
end;

function TChild.method: integer;
begin
 method := 11;
end;

function TVirtualParent.method: integer;
begin
 method := 20;
end;

function TOverriddenChild.method: integer;
begin
 method := 21;
end;

function TNotOverriddenChild.method: integer;
begin
 method := 22;
end;

function TAbstractChild.method: integer;
begin
 method := 31;
end;

function TOverloadClass.method: integer;
begin
 method := 41;
end;

function TOverloadClass.method (v: integer): integer;
begin
 method := 42 + v;
end;

procedure TOtherParent.method;
begin
 writeln('TOtherParent.method()');
end;

procedure TInheritedClass.method;
begin
 (* Aqui chamamos TOtherParent.method() *)
 inherited;
 writeln('TInheritedClass.method()');
end;

var
 Pp, Pc: TParent;
 C: TChild;
 Vp, Vca, Vcb: TVirtualParent;
 Cva: TOverriddenChild;
 Cvb: TNotOverriddenChild;
 Ac: TAbstractChild;
 Oc: TOverloadClass;
 Op: TOtherParent;
 Ic: TInheritedClass;

begin
 (* O problema com estas 3 chamadas iniciais é o seguinte: 'method' foi
    declarado como um método estático (ie. não 'virtual').
    O que isso significa é que se eu instanciar um TChild como sendo TParent,
    os métodos serão os de TParent e não os de TChild, como acontece com o
    objecto 'Pc' abaixo. *)
 Pp := TParent.Create();
 writeln('TParent.method(): ', Pp.method());

 C := TChild.Create();
 writeln('TChild.method(): ', C.method());

 Pc := TParent(TChild.Create());
 writeln('TParent(TChild).method(): ', Pc.method());

 (* As próximas 3 chamadas incluem classes onde 'method' foi declarado como
    'virtual' na classe base e como 'override' na subclasse.
    O resultado é que se eu instanciar um TOverridenChild como sendo um
    TVirtualParent, os seus métodos serão os que foram definidos (overridden)
    pela subclasse, e não os da TVirtualParent (ver Vca). *)
 Vp := TVirtualParent.Create();
 writeln('TVirtualParent.method(): ', Vp.method());

 Cva := TOverriddenChild.Create();
 writeln('TOverriddenChild.method(): ', Cva.method());

 Vca := TVirtualParent(TOverriddenChild.Create());
 writeln('TVirtualParent(TOverriddenChild).method(): ', Vca.method());

 (* Aqui no caso da TNotOverridenChild o que acontece é que apesar da
    TVirtualParent ter 'method' como virtual, na TNotOverridenChild não está
    como 'override', e por isso, instanciando TNotOverridenChild como
    TVirtualParent, será o method de TVirtualParent a ser executado. *)
 Cvb := TNotOverriddenChild.Create();
 writeln('TNotOverriddenChild.method(): ', Cvb.method());

 Vcb := TVirtualParent(TNotOverriddenChild.Create());
 writeln('TVirtualParent(TNotOverriddenChild).method(): ', Vcb.method());

 (* Este objecto é um TAbstractChild, uma subclasse que implementa um método
    definido como 'abstract' na classe base TAbstractClass.
    Os métodos definidos como 'abstract' só precisam de ser implementados em
    subclasses. *)
 Ac := TAbstractChild.Create();
 writeln('TAbstractChild.method(): ', Ac.method());

 Oc := TOverloadClass.Create();
 writeln('TOverloadClass.method(): ', Oc.method());
 writeln('TOverloadClass.method(7): ', Oc.method(7));

 (* Basicamente, TOtherParent é uma classe como outra qualquer *)
 Op := TOtherParent.Create();
 writeln;
 writeln('TOtherParent.method():');
 Op.method();

 (* No entanto, quando chamamos TInheritedClass.method(), este irá chamar
    dentro de si TOtherParent.method() (ver acima na implementação) *)
 Ic := TInheritedClass.Create();
 writeln;
 writeln('TInheritedClass.method():');
 Ic.method();


 Pp.Destroy();
 Pc.Destroy();
 C.Destroy();
 Vp.Destroy();
 Vca.Destroy();
 Vcb.Destroy;
 Cva.Destroy();
 Cvb.Destroy();
 Ac.Destroy();
 Oc.Destroy();
 Op.Destroy();
 Ic.Destroy();
end.

Cá está o output:

pwseo@laptop:~$ ./oop
TParent.method(): 10
TChild.method(): 11
TParent(TChild).method(): 10
TVirtualParent.method(): 20
TOverriddenChild.method(): 21
TVirtualParent(TOverriddenChild).method(): 21
TNotOverriddenChild.method(): 22
TVirtualParent(TNotOverriddenChild).method(): 20
TAbstractChild.method(): 31
TOverloadClass.method(): 41
TOverloadClass.method(7): 49

TOtherParent.method():
TOtherParent.method()

TInheritedClass.method():
TOtherParent.method()
TInheritedClass.method()

Penso não me ter enganado.

Editado por pwseo
Correcção da LP

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites

Crie uma conta ou ligue-se para comentar

Só membros podem comentar

Criar nova conta

Registe para ter uma conta na nossa comunidade. É fácil!

Registar nova conta

Entra

Já tem conta? Inicie sessão aqui.

Entrar Agora

×

Aviso Sobre Cookies

Ao usar este site você aceita os nossos Termos de Uso e Política de Privacidade. Este site usa cookies para disponibilizar funcionalidades personalizadas. Para mais informações visite esta página.