Já existe uma resposta aceita, mas eu queria apontar algumas coisas.
Os objetos devem ter um comportamento que eles executam. Caso contrário, há poucas razões para usar um sobre uma estrutura. Seus objetos de exemplo não têm comportamento algum, apenas os dados que você tornou mais complicado para acessar via getters e setters. Seu exemplo viola Liskov principalmente porque não é orientado a objetos de qualquer maneira. É processualmente lendo e escrevendo dados com uma estrutura específica.
Também quero mencionar que "Objetos Nulos" não significam null
ponteiros. Eles significam uma implementação da classe com um comportamento padrão. Veja este vídeo .
Então, como poderia ser o LSP?
Se eu pensar em Shape2D em termos de qual comportamento eu quero fazer para mim, uma coisa que vem à mente é o cálculo de área. Porque isso será diferente nas formas.
public class Shape2D
{
public virtual double GetArea() { return 0.0; }
}
Aqui, estou usando o Shape2D como seu próprio Objeto Nulo - uma forma que não tem área. Pode ser estendido pela subclasse e sobrescrevendo GetArea()
.
Returning double
as the area is a questionable choice. Because there are other implications with area, like the unit of measure or that it can be infinite. Maybe an Area class should be created too if this were production code.
Então, vamos criar algumas implementações de coisas de área bidimensional.
public class Square : Shape2D
{
private double _sideLength;
public Square(double sideLength) { _sideLength = sideLength; }
public override double GetArea()
{ return _sideLength ^ 2 }
}
public class Rectangle : Shape2D
{
private double _length;
private double _height;
public Rectangle(double length, double height) { ... }
public override double GetArea()
{ return _length * _height; }
}
public class Circle : Shape2D
{
private double _radius;
public Circle(double radius) { _radius = radius; }
public override double GetArea()
{ return Math.Pi * (_radius ^ 2); }
}
Agora, neste ponto, você pode estar se perguntando. "Por que eu não apenas calculo a área no construtor?" ou "Por que não apenas criar um método estático para diferentes cálculos de área?" Bem, e quanto a formas 2D complexas? Como um octógono côncavo definido por vetores? Talvez a implementação particular da forma seja computacionalmente cara para calcular a área? Eu acho que, em última análise, requer alguma intuição para determinar qual caminho está correto. Mas no exemplo aqui, estamos calculando apenas quando solicitado.
Na pergunta original, você alterou a largura do retângulo alterando dados particulares sobre o retângulo. Mas aqui, você criaria um new
Rectangle com dimensões diferentes.
Portanto, isso segue o LSP, pois para qualquer método que tenha um Shape2D
, você poderia passar um Square
, Shape2D
(Null Object) ou qualquer forma que pudesse ser implementada no futuro como ConcaveOctagon
sem o método sabendo disso.