38 - POO - Parte private, protected y public

Los campos y métodos de una clase pueden definirse de tipo public, protected y private, por defecto siempre son públicos.

Un método o campo público puede ser accedido desde fuera de la clase.

Una de las ventajas de la programación orientada a objetos es permitir encapsular datos y responsabilidades dentro de una clase.

Cuando uno planea una clase tiene que definir que quiero que se muestre hacia afuera y que quiero que quede oculto.

Normalmente los campos de una clase se definen dentro de la zona private o protected. Los métodos se definen públicos si queremos que sean accesibles desde fuera y privados o protegidos cuando solo quiero que puedan ser llamados desde la misma clase o subclase.

Si bien podemos definir todo público como venimos trabajando en los conceptos anteriores el agregado de estos modificadores nos permitirá crear clases más fáciles de reutilizar.

Resumiendo

  • private: El modificador de acceso privado especifica campos y métodos de una clase que no son accesible fuera de la unidad donde se declara la clase.
  • public: El modificador de acceso público denota campos y métodos que son de libre acceso desde cualquier otra parte de un programa.
  • protected: El modificador de acceso protegido se utiliza para indicar métodos y campos con visibilidad sólo en la clase actual y sus clases derivadas (o subclases)

Problema 1

Plantear una clase llamada TDado. Definir un campo llamado Valor y tres métodos uno privado que dibuje una línea de asteríscos y otro dos públicos, uno que genere un número aleatorio entre 1 y 6 y otro que lo imprima llamando en este último al que dibuja la línea de asteríscos.
Declarar la clase TDado en una unidad.

Luego de crear el proyecto138 procedemos a crear una unidad como ya vimos en conceptos anteriores:

creación de una unidad en Delphi

Unit1.pas

unit Unit1;

interface

type
  TDado = class
  private
    Valor: Integer;
    procedure Separador;
  public
    procedure Tirar;
    procedure Imprimir;
  end;

implementation

procedure TDado.Separador;
begin
  WriteLn('********************');
end;

procedure TDado.Tirar;
begin
  Valor := Random(6) + 1;
end;

procedure TDado.Imprimir;
begin
  Separador;
  WriteLn(Valor);
  Separador;
end;

end.

Proyecto138

program Proyecto138;

{$APPTYPE CONSOLE}


uses
  Unit1 in 'Unit1.pas';

var
  Dado1: TDado;
begin
  Randomize;
  WriteLn('Prueba del dado');
  Dado1 := TDado.Create;
  Dado1.Tirar;
  Dado1.Imprimir;
  Dado1.Free;
  ReadLn;
end.

Disponemos de la palabra clave private para indicar que los campos y métodos siguientes solo se podrán acceder dentro de la misma clase:

  TDado = class
  private
    Valor: Integer;
    procedure Separador;

Ahora si debemos disponer de la palabra clave public para identificar que métodos serán públicos:

  public
    procedure Tirar;
    procedure Imprimir;

La codificación de los métodos en la zona de la implementation de la unidad no varía en nada a lo ya visto.

Ahora si hay que tener en cuenta que en el archivo Proyecto138.dpr donde definimos un objeto de la clase TDado solo tenemos acceso a los métodos públicos:

var
  Dado1: TDado;
begin
  Randomize;
  WriteLn('Prueba del dado');
  Dado1 := TDado.Create;
  Dado1.Tirar;
  Dado1.Imprimir;
  Dado1.Free;

Si intentamos acceder a un campo o método público se genera un error sintáctico:

Error acceso campo privado

Es decir que el objeto Dado1 solo puede llamar a los métodos Tirar e Imprimir que son los públicos.

Tiene grandes ventajas el encapsulamiento, como vemos al no poder asignar un valor al valor del dado desde fuera de la clase, luego si se genera un valor equivocado en el dado el problema se encuentra dentro de la clase TDado.

Acotaciones

Lo más común es definir distintas unidades para cada clase, pero si codificamos la clase en el mismo archivo Proyecto138.dpr luego los objetos que definimos en ese archivo tienen acceso a los campos y métodos privados:

program Proyecto138;

{$APPTYPE CONSOLE}


type
  TDado = class
  private
    Valor: Integer;
    procedure Separador;
  public
    procedure Tirar;
    procedure Imprimir;
  end;

procedure TDado.Separador;
begin
  WriteLn('********************');
end;

procedure TDado.Tirar;
begin
  Valor := Random(6) + 1;
end;

procedure TDado.Imprimir;
begin
  Separador;
  WriteLn(Valor);
  Separador;
end;


var
  Dado1: TDado;
begin
  Randomize;
  WriteLn('Prueba del dado');
  Dado1 := TDado.Create;
  Dado1.Valor := 5;
  Dado1.Imprimir;
  Dado1.Free;
  ReadLn;
end.

Este programa no genera error ya que la clase TDado se encuentra definida en el mismo archivo donde definimos el objeto Dado1.

En las últimos versiones de Delphi se ha agregado el modificador strict private y strict protected para salvar las situaciones donde se definen objetos de dicha clase en el mismo archivo:

type
  TDado = class
  strict private
    Valor: Integer;
    procedure Separador;
  public
    procedure Tirar;
    procedure Imprimir;
  end;

Problema 2

Declarar una clase llamada TCalculadora que disponga de dos métodos para la carga de valores de tipo Double (almacenar los datos en dos campos)
Definir las responsabilidades de sumar, restar, multiplicar, dividir e imprimir.

Declarar luego una clase llamada TCalculadoraCientifica que herede de TCalculadora y añada las responsabilidades de calcular el cuadrado del primer número y la raíz cuadrada.

Declarar las dos clases en una unidad.
Definir los campos Valor1, Valor2 y Resultado en la zona protected de la clase.

Unit1.pas

unit Unit1;

interface

type
  TCalculadora = class
  protected
    Valor1: Double;
    Valor2: Double;
    Resultado: Double;
  public
    procedure Cargar1;
    procedure Cargar2;
    procedure Sumar;
    procedure Restar;
    procedure Multiplicar;
    procedure Dividir;
    procedure Imprimir(mensaje: String);
  end;

  TCalculadoraCientifica = class(TCalculadora)
    procedure Cuadrado;
    procedure Raiz;
  end;

implementation


procedure TCalculadora.Cargar1;
begin
  Write('Ingrese valor:');
  ReadLn(Valor1);
end;

procedure TCalculadora.Cargar2;
begin
  Write('Ingrese valor:');
  ReadLn(Valor2);
end;

procedure TCalculadora.Sumar;
begin
  Resultado := Valor1 + Valor2;
end;

procedure TCalculadora.Restar;
begin
  Resultado := Valor1 - Valor2
end;

procedure TCalculadora.Multiplicar;
begin
  Resultado := Valor1 * Valor2;
end;

procedure TCalculadora.Dividir;
begin
  Resultado := Valor1 / Valor2;
end;

procedure TCalculadora.Imprimir(mensaje: String);
begin
  WriteLn(mensaje,':', Resultado:0:2);
end;

procedure TCalculadoraCientifica.Cuadrado;
begin
  Resultado := Valor1 * Valor1;
end;

procedure TCalculadoraCientifica.Raiz;
begin
  Resultado := Sqrt(Valor1);
end;

end.

Proyecto139

program Proyecto139;

{$APPTYPE CONSOLE}

uses
  Unit1 in 'Unit1.pas';

var
  Calculadora1: TCalculadora;
  CalculadoraCientifica1: TCalculadoraCientifica;
begin
  WriteLn('Uso de la calculadora');
  Calculadora1 := TCalculadora.Create;
  Calculadora1.Cargar1;
  Calculadora1.Cargar2;
  Calculadora1.Sumar;
  Calculadora1.Imprimir('Suma');
  Calculadora1.Restar;
  Calculadora1.Imprimir('Resta');
  Calculadora1.Multiplicar;
  Calculadora1.Imprimir('Multiplicación');
  Calculadora1.Dividir;
  Calculadora1.Imprimir('Division');
  Calculadora1.Free;
  WriteLn('Uso de la calculadora científica');
  CalculadoraCientifica1 := TCalculadoraCientifica.Create;
  CalculadoraCientifica1.Cargar1;
  CalculadoraCientifica1.Cargar2;
  CalculadoraCientifica1.Sumar;
  CalculadoraCientifica1.Imprimir('Suma');
  CalculadoraCientifica1.Restar;
  CalculadoraCientifica1.Imprimir('Resta');
  CalculadoraCientifica1.Multiplicar;
  CalculadoraCientifica1.Imprimir('Multiplicación');
  CalculadoraCientifica1.Dividir;
  CalculadoraCientifica1.Imprimir('Division');
  CalculadoraCientifica1.Cuadrado;
  CalculadoraCientifica1.Imprimir('Cuadrado del primer valor');
  CalculadoraCientifica1.Raiz;
  CalculadoraCientifica1.Imprimir('Raiz del primer valor');
  CalculadoraCientifica1.Free;
  ReadLn;
end.

La clase TCalculadora define en la zona protected los tres campos para que puedan ser accedidos solo por la clase TCalculadora o cualquier sublclase:

  TCalculadora = class
  protected
    Valor1: Double;
    Valor2: Double;
    Resultado: Double;
  public
    procedure Cargar1;
    procedure Cargar2;
    procedure Sumar;
    procedure Restar;
    procedure Multiplicar;
    procedure Dividir;
    procedure Imprimir(mensaje: String);
  end;

Los métodos de la clase TCalculadoraCientifica pueden acceder a los campos protected de la clase padre:

procedure TCalculadoraCientifica.Cuadrado;
begin
  Resultado := Valor1 * Valor1;
end;

Pero los objetos que se definan de la clase TCalculadora y TCalculadoraCientifica solo podrán acceder a los métodos públicos:

  WriteLn('Uso de la calculadora');
  Calculadora1 := TCalculadora.Create;
  Calculadora1.Valor1 := 100; // Error de compilación debido al intento de acceder a un campo protegido

Hay que tener en cuenta que no podemos definir Valor1, Valor2 y Resultado en la zona private ya que esto hará que la subclase TCalculadoraCientifica no tenga acceso a dichos campos.

Problema propuesto

  • Desarrollar una clase llamada TNumerosAleatorios que defina un campo privado de tipo TVector de 5 enteros. En un método privado cargar valores aleatorios comprendidos entre 1 y 10. Llamar a este método desde el constructor Create.
    Definir otros tres métodos públicos que muestren el vector, el mayor y el menor elemento.
    Declarar la clase en una unidad separada.
Solución
unit Unit1;

interface

type

TVector = array[1..5] of Integer;

TNumerosAleatorios = class
  private
    Vector: TVector;
    procedure Cargar;
  public
    constructor Create;
    procedure Imprimir;
    procedure MostrarMayor;
    procedure MostrarMenor;
end;

implementation

procedure TNumerosAleatorios.Cargar;
var
  f: Integer;
begin
  for f := 1 to 5 do
    Vector[f] := Random(10) + 1;
end;

constructor TNumerosAleatorios.Create;
begin
  Cargar;
end;

procedure TNumerosAleatorios.Imprimir;
var
  f: Integer;
begin
  for f:=1 to 5 do
    Write(Vector[f],' ');
  WriteLn;
end;

procedure TNumerosAleatorios.MostrarMayor;
var
  f: Integer;
  mayor: Integer;
begin
  mayor := Vector[1];
  for f:=2 to 5 do
    if Vector[f] > mayor then
      mayor := Vector[f];
  WriteLn('El elemento mayor del vector es:', mayor);
end;

procedure TNumerosAleatorios.MostrarMenor;
var
  f: Integer;
  menor: Integer;
begin
  menor := Vector[1];
  for f:=2 to 5 do
    if Vector[f] < menor then
      menor := Vector[f];
  WriteLn('El elemento menor del vector es:', menor);
end;

end.



program Proyecto140;

{$APPTYPE CONSOLE}

uses
  Unit1 in 'Unit1.pas';

var
  NumerosAleatorios1: TNumerosAleatorios;
begin
  Randomize;
  NumerosAleatorios1 := TNumerosAleatorios.Create;
  NumerosAleatorios1.Imprimir;
  NumerosAleatorios1.MostrarMayor;
  NumerosAleatorios1.MostrarMenor;
  NumerosAleatorios1.Free;
  ReadLn;
end.