Você pode explicar o Princípio de Substituição de Liskov (O 'L' de SOLID) com um bom exemplo C # cobrindo todos os aspectos do princípio de uma forma simplificada? Se for realmente possível.
Você pode explicar o Princípio de Substituição de Liskov (O 'L' de SOLID) com um bom exemplo C # cobrindo todos os aspectos do princípio de uma forma simplificada? Se for realmente possível.
Respostas:
(Esta resposta foi reescrita em 13/05/2013, leia a discussão na parte inferior dos comentários)
LSP trata de seguir o contrato da classe base.
Você pode, por exemplo, não lançar novas exceções nas subclasses, pois aquele que usa a classe base não esperaria isso. O mesmo é válido se a classe base lançar ArgumentNullException
se um argumento estiver faltando e a subclasse permitir que o argumento seja nulo, também uma violação do LSP.
Aqui está um exemplo de uma estrutura de classe que viola o LSP:
public interface IDuck
{
void Swim();
// contract says that IsSwimming should be true if Swim has been called.
bool IsSwimming { get; }
}
public class OrganicDuck : IDuck
{
public void Swim()
{
//do something to swim
}
bool IsSwimming { get { /* return if the duck is swimming */ } }
}
public class ElectricDuck : IDuck
{
bool _isSwimming;
public void Swim()
{
if (!IsTurnedOn)
return;
_isSwimming = true;
//swim logic
}
bool IsSwimming { get { return _isSwimming; } }
}
E o código de chamada
void MakeDuckSwim(IDuck duck)
{
duck.Swim();
}
Como você pode ver, existem dois exemplos de patos. Um pato orgânico e um pato elétrico. O pato elétrico só pode nadar se estiver ligado. Isso quebra o princípio LSP, uma vez que deve ser ativado para poder nadar, pois IsSwimming
(que também faz parte do contrato) não será definido como na classe base.
Claro, você pode resolvê-lo fazendo algo assim
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
Mas isso quebraria o princípio Aberto / Fechado e tem que ser implementado em todos os lugares (e portanto ainda gera código instável).
A solução adequada seria ligar automaticamente o pato no Swim
método e, ao fazer isso, fazer com que o pato elétrico se comporte exatamente como definido pela IDuck
interface
Atualizar
Alguém adicionou um comentário e o removeu. Tinha um ponto válido que eu gostaria de abordar:
A solução com ativar o pato dentro do Swim
método pode ter efeitos colaterais ao trabalhar com a implementação real ( ElectricDuck
). Mas isso pode ser resolvido usando uma implementação de interface explícita . imho é mais provável que você tenha problemas ao NÃO ligá-lo, Swim
pois é esperado que ele nade ao usar a IDuck
interface
Atualização 2
Reformulei algumas partes para ficar mais claro.
if duck is ElectricDuck
parte. Tive um seminário sobre SOLID na quinta-feira passada :)
as
palavra - chave, o que realmente os salva de muitas verificações de tipo. Estou pensando algo como o seguinte:if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
LSP uma abordagem prática
Em todos os lugares que procuro por exemplos de C # do LSP, as pessoas usaram classes e interfaces imaginárias. Aqui está a implementação prática do LSP que implementei em um de nossos sistemas.
Cenário: suponha que temos 3 bancos de dados (clientes hipotecários, clientes de contas correntes e clientes de contas poupança) que fornecem dados do cliente e precisamos dos detalhes do cliente para o sobrenome do cliente fornecido. Agora podemos obter mais de 1 detalhe do cliente desses 3 bancos de dados com o sobrenome fornecido.
Implementação:
CAMADA DE MODELO DE NEGÓCIO:
public class Customer
{
// customer detail properties...
}
CAMADA DE ACESSO DE DADOS:
public interface IDataAccess
{
Customer GetDetails(string lastName);
}
A interface acima é implementada pela classe abstrata
public abstract class BaseDataAccess : IDataAccess
{
/// <summary> Enterprise library data block Database object. </summary>
public Database Database;
public Customer GetDetails(string lastName)
{
// use the database object to call the stored procedure to retrieve the customer details
}
}
Esta classe abstrata tem um método comum "GetDetails" para todos os 3 bancos de dados que é estendido por cada uma das classes de banco de dados conforme mostrado abaixo
ACESSO A DADOS DO CLIENTE DE HIPOTECA:
public class MortgageCustomerDataAccess : BaseDataAccess
{
public MortgageCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetMortgageCustomerDatabase();
}
}
ACESSO DE DADOS DO CLIENTE DA CONTA ATUAL:
public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetCurrentAccountCustomerDatabase();
}
}
ACESSO A DADOS DO CLIENTE NA CONTA DE ECONOMIA:
public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetSavingsAccountCustomerDatabase();
}
}
Uma vez que essas 3 classes de acesso a dados são definidas, agora chamamos nossa atenção para o cliente. Na camada Business, temos a classe CustomerServiceManager que retorna os detalhes do cliente para seus clientes.
CAMADA DE NEGÓCIOS:
public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager
{
public IEnumerable<Customer> GetCustomerDetails(string lastName)
{
IEnumerable<IDataAccess> dataAccess = new List<IDataAccess>()
{
new MortgageCustomerDataAccess(new DatabaseFactory()),
new CurrentAccountCustomerDataAccess(new DatabaseFactory()),
new SavingsAccountCustomerDataAccess(new DatabaseFactory())
};
IList<Customer> customers = new List<Customer>();
foreach (IDataAccess nextDataAccess in dataAccess)
{
Customer customerDetail = nextDataAccess.GetDetails(lastName);
customers.Add(customerDetail);
}
return customers;
}
}
Não mostrei a injeção de dependência para mantê-la simples, pois já está ficando complicada.
Agora, se tivermos um novo banco de dados de detalhes do cliente, podemos simplesmente adicionar uma nova classe que estende BaseDataAccess e fornece seu objeto de banco de dados.
Claro, precisamos de procedimentos armazenados idênticos em todos os bancos de dados participantes.
Por fim, o cliente da CustomerServiceManager
classe só chamará o método GetCustomerDetails, passará o lastName e não se preocupará com como e de onde os dados estão vindo.
Espero que isso lhe dê uma abordagem prática para entender o LSP.
Aqui está o código para aplicar o Princípio do Substituto de Liskov.
public abstract class Fruit
{
public abstract string GetColor();
}
public class Orange : Fruit
{
public override string GetColor()
{
return "Orange Color";
}
}
public class Apple : Fruit
{
public override string GetColor()
{
return "Red color";
}
}
class Program
{
static void Main(string[] args)
{
Fruit fruit = new Orange();
Console.WriteLine(fruit.GetColor());
fruit = new Apple();
Console.WriteLine(fruit.GetColor());
}
}
LSV afirma: "As classes derivadas devem ser substituíveis por suas classes básicas (ou interfaces)" & "Métodos que usam referências a classes básicas (ou interfaces) devem ser capazes de usar métodos das classes derivadas sem saber sobre isso ou sem conhecer os detalhes . "