Esta es la solución de Nelo: http://nelopauselli.blogspot.com.ar/2012/08/implementando-solid-lsp.html
El código del ejercicio es este:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestFixture] | |
class MailBuilderTest | |
{ | |
[Test] | |
public void TestContactInformation() | |
{ | |
// arrange | |
ContactInformation contactInformation = new ContactInformation() | |
{ | |
FirstName = "Homero", | |
LastName = "Simpson" | |
}; | |
IMailMessageBuilder<ContactInformation> mailBuilder = new ContactInformationMailMessageBuilder(); | |
// act | |
MailMessage message = mailBuilder | |
.WithFrom("cliente@gmail.com") | |
.WithTo("empresa@gmail.com") | |
.WithSubject("Hola") | |
.WithEntity(contactInformation).BuildMessage(); | |
// assert | |
Assert.That(message.From.Address, Is.EqualTo("cliente@gmail.com")); | |
Assert.That(message.To, Has.All.Matches<MailAddress>(x => x.Address == "empresa@gmail.com")); | |
Assert.That(message.Subject, Is.EqualTo("Hola")); | |
Assert.That(message.Body, Is.StringContaining("Nombre: Homero")); | |
Assert.That(message.Body, Is.StringContaining("Apellido: Simpson")); | |
} | |
[Test] | |
public void TestContactInformationSubsidiary() | |
{ | |
// arrange | |
ContactInformationSubsidiary contactInformation = new ContactInformationSubsidiary() | |
{ | |
FirstName = "Homero", | |
LastName = "Simpson", | |
Subsidiary = "Retiro" | |
}; | |
IMailMessageBuilder<ContactInformation> mailBuilder = new ContactInformationMailMessageBuilder(); | |
// act | |
MailMessage message = mailBuilder | |
.WithFrom("cliente@gmail.com") | |
.WithTo("empresa@gmail.com") | |
.WithSubject("Hola") | |
.WithEntity(contactInformation).BuildMessage(); | |
// assert | |
Assert.That(message.From.Address, Is.EqualTo("cliente@gmail.com")); | |
Assert.That(message.To, Has.All.Matches<MailAddress>(x => x.Address == "empresa@gmail.com")); | |
Assert.That(message.Subject, Is.EqualTo("Hola")); | |
Assert.That(message.Body, Is.StringContaining("Nombre: Homero")); | |
Assert.That(message.Body, Is.StringContaining("Apellido: Simpson")); | |
Assert.That(message.Body, Is.StringContaining("Sucursal: Retiro")); | |
} | |
[Test] | |
public void TestContactInformationAuctionSaleArtWork() | |
{ | |
// arrange | |
ContactInformation contactInformation = new ContactInformationAuction() | |
{ | |
FirstName = "Homero", | |
LastName = "Simpson", | |
Author = "Picasso", | |
Dimensions = "3x3" | |
}; | |
IMailMessageBuilder<ContactInformation> mailBuilder = new ContactInformationMailMessageBuilder(); | |
// act | |
MailMessage message = mailBuilder | |
.WithFrom("cliente@gmail.com") | |
.WithTo("empresa@gmail.com") | |
.WithSubject("Hola") | |
.WithEntity(contactInformation).BuildMessage(); | |
// assert | |
Assert.That(message.From.Address, Is.EqualTo("cliente@gmail.com")); | |
Assert.That(message.To, Has.All.Matches<MailAddress>(x => x.Address == "empresa@gmail.com")); | |
Assert.That(message.Subject, Is.EqualTo("Hola")); | |
Assert.That(message.Body, Is.StringContaining("Nombre: Homero")); | |
Assert.That(message.Body, Is.StringContaining("Apellido: Simpson")); | |
Assert.That(message.Body, Is.StringContaining("Autor: Picasso")); | |
Assert.That(message.Body, Is.StringContaining("Dimensiones: 3x3")); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public IMailMessageBuilder<ContactInformation> WithEntity(ContactInformation contactInformation) | |
{ | |
ParseContactInformation(contactInformation); | |
if (contactInformation is ContactInformationSubsidiary) | |
{ | |
ContactInformationSubsidiary contact = contactInformation as ContactInformationSubsidiary; | |
ParseContactInformationSubsidiary(contact); | |
} | |
else if (contactInformation is ContactInformationAuction) | |
{ | |
ContactInformationAuction contact = contactInformation as ContactInformationAuction; | |
ParseContactInformationAuction(contact); | |
} | |
return this; | |
} |
El problema es que el método WithEntity hace un tratamiento especial para cada una de las subclases. El principio de Liskov nos dice que si dependemos de una clase base, debemos poder usar objetos de clases derivadas sin saberlo. Y este no es el caso. Los problemas que presenta no seguir este principio son:
- Quien usa el método WIthEnity puede pensar, con todo derecho, que pasándole cualquier objeto derivado de ContactInformation va a funcionar.
- Quien agrega otro subtipo de ContactInformation, puede olvidarse que existe este método WithEntity con tratamiento especial y usarlo.
- El ejemplo está reducido y simplificado p/poder ser entendido fácilmente. En realidad se trata de un caso real, con muchos subtipos y una jerarquía algo más compleja. Modificar esta MailBuilder era realmente un problema, ya que implicaba volver a probar todos los casos.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ContactInformationAuctionMessageBuilder : IMailMessageBuilder<ContactInformationAuction> | |
{ | |
private MailMessage mailMessage; | |
private StringBuilder body = new StringBuilder(); | |
public ContactInformationAuctionMessageBuilder() | |
{ | |
mailMessage = new MailMessage(); | |
} | |
public IMailMessageBuilder<ContactInformationAuction> WithTo(string to) | |
{ | |
mailMessage.To.Add(to); | |
return this; | |
} | |
public IMailMessageBuilder<ContactInformationAuction> WithSubject(string subject) | |
{ | |
mailMessage.Subject = subject; | |
return this; | |
} | |
public IMailMessageBuilder<ContactInformationAuction> WithFrom(string from) | |
{ | |
mailMessage.From = new MailAddress(from); | |
return this; | |
} | |
public MailMessage BuildMessage() | |
{ | |
mailMessage.Body = body.ToString(); | |
return mailMessage; | |
} | |
public IMailMessageBuilder<ContactInformationAuction> WithEntity(ContactInformationAuction contact) | |
{ | |
AddBodyLine("Nombre: {0}", contact.FirstName); | |
AddBodyLine("Apellido: {0}", contact.LastName); | |
AddBodyLine("Autor: {0}", contact.Author); | |
AddBodyLine("Dimensiones: {0}", contact.Dimensions); | |
return this; | |
} | |
private void AddBodyLine(String line, params object[] args) | |
{ | |
body.AppendLine(String.Format(line, args)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ContactInformationMessageBuilder : IMailMessageBuilder<ContactInformation> | |
{ | |
private MailMessage mailMessage; | |
private StringBuilder body = new StringBuilder(); | |
public ContactInformationMessageBuilder() | |
{ | |
mailMessage = new MailMessage(); | |
} | |
public IMailMessageBuilder<ContactInformation> WithTo(string to) | |
{ | |
mailMessage.To.Add(to); | |
return this; | |
} | |
public IMailMessageBuilder<ContactInformation> WithSubject(string subject) | |
{ | |
mailMessage.Subject = subject; | |
return this; | |
} | |
public IMailMessageBuilder<ContactInformation> WithFrom(string from) | |
{ | |
mailMessage.From = new MailAddress(from); | |
return this; | |
} | |
public MailMessage BuildMessage() | |
{ | |
mailMessage.Body = body.ToString(); | |
return mailMessage; | |
} | |
public IMailMessageBuilder<ContactInformation> WithEntity(ContactInformation contact) | |
{ | |
AddBodyLine("Nombre: {0}", contact.FirstName); | |
AddBodyLine("Apellido: {0}", contact.LastName); | |
return this; | |
} | |
private void AddBodyLine(String line, params object[] args) | |
{ | |
body.AppendLine(String.Format(line, args)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ContactInformationSubsidiaryMessageBuilder : IMailMessageBuilder<ContactInformationSubsidiary> | |
{ | |
private MailMessage mailMessage; | |
private StringBuilder body = new StringBuilder(); | |
public ContactInformationSubsidiaryMessageBuilder() | |
{ | |
mailMessage = new MailMessage(); | |
} | |
public IMailMessageBuilder<ContactInformationSubsidiary> WithTo(string to) | |
{ | |
mailMessage.To.Add(to); | |
return this; | |
} | |
public IMailMessageBuilder<ContactInformationSubsidiary> WithSubject(string subject) | |
{ | |
mailMessage.Subject = subject; | |
return this; | |
} | |
public IMailMessageBuilder<ContactInformationSubsidiary> WithFrom(string from) | |
{ | |
mailMessage.From = new MailAddress(from); | |
return this; | |
} | |
public MailMessage BuildMessage() | |
{ | |
mailMessage.Body = body.ToString(); | |
return mailMessage; | |
} | |
public IMailMessageBuilder<ContactInformationSubsidiary> WithEntity(ContactInformationSubsidiary contact) | |
{ | |
AddBodyLine("Nombre: {0}", contact.FirstName); | |
AddBodyLine("Apellido: {0}", contact.LastName); | |
AddBodyLine("Sucursal: {0}", contact.Subsidiary); | |
return this; | |
} | |
private void AddBodyLine(String line, params object[] args) | |
{ | |
body.AppendLine(String.Format(line, args)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestFixture] | |
class MailBuilderTest | |
{ | |
[Test] | |
public void TestContactInformation() | |
{ | |
// arrange | |
ContactInformation contactInformation = new ContactInformation() | |
{ | |
FirstName = "Homero", | |
LastName = "Simpson" | |
}; | |
IMailMessageBuilder<ContactInformation> mailBuilder = new ContactInformationMessageBuilder(); | |
// act | |
MailMessage message = mailBuilder | |
.WithFrom("cliente@gmail.com") | |
.WithTo("empresa@gmail.com") | |
.WithSubject("Hola") | |
.WithEntity(contactInformation).BuildMessage(); | |
// assert | |
Assert.That(message.From.Address, Is.EqualTo("cliente@gmail.com")); | |
Assert.That(message.To, Has.All.Matches<MailAddress>(x => x.Address == "empresa@gmail.com")); | |
Assert.That(message.Subject, Is.EqualTo("Hola")); | |
Assert.That(message.Body, Is.StringContaining("Nombre: Homero")); | |
Assert.That(message.Body, Is.StringContaining("Apellido: Simpson")); | |
} | |
[Test] | |
public void TestContactInformationSubsidiary() | |
{ | |
// arrange | |
ContactInformationSubsidiary contactInformation = new ContactInformationSubsidiary() | |
{ | |
FirstName = "Homero", | |
LastName = "Simpson", | |
Subsidiary = "Retiro" | |
}; | |
IMailMessageBuilder<ContactInformationSubsidiary> mailBuilder = new ContactInformationSubsidiaryMessageBuilder(); | |
// act | |
MailMessage message = mailBuilder | |
.WithFrom("cliente@gmail.com") | |
.WithTo("empresa@gmail.com") | |
.WithSubject("Hola") | |
.WithEntity(contactInformation).BuildMessage(); | |
// assert | |
Assert.That(message.From.Address, Is.EqualTo("cliente@gmail.com")); | |
Assert.That(message.To, Has.All.Matches<MailAddress>(x => x.Address == "empresa@gmail.com")); | |
Assert.That(message.Subject, Is.EqualTo("Hola")); | |
Assert.That(message.Body, Is.StringContaining("Nombre: Homero")); | |
Assert.That(message.Body, Is.StringContaining("Apellido: Simpson")); | |
Assert.That(message.Body, Is.StringContaining("Sucursal: Retiro")); | |
} | |
[Test] | |
public void TestContactInformationAuction() | |
{ | |
// arrange | |
ContactInformationAuction contactInformation = new ContactInformationAuction() | |
{ | |
FirstName = "Homero", | |
LastName = "Simpson", | |
Author = "Picasso", | |
Dimensions = "3x3" | |
}; | |
IMailMessageBuilder<ContactInformationAuction> mailBuilder = new ContactInformationAuctionMessageBuilder(); | |
// act | |
MailMessage message = mailBuilder | |
.WithFrom("cliente@gmail.com") | |
.WithTo("empresa@gmail.com") | |
.WithSubject("Hola") | |
.WithEntity(contactInformation).BuildMessage(); | |
// assert | |
Assert.That(message.From.Address, Is.EqualTo("cliente@gmail.com")); | |
Assert.That(message.To, Has.All.Matches<MailAddress>(x => x.Address == "empresa@gmail.com")); | |
Assert.That(message.Subject, Is.EqualTo("Hola")); | |
Assert.That(message.Body, Is.StringContaining("Nombre: Homero")); | |
Assert.That(message.Body, Is.StringContaining("Apellido: Simpson")); | |
Assert.That(message.Body, Is.StringContaining("Autor: Picasso")); | |
Assert.That(message.Body, Is.StringContaining("Dimensiones: 3x3")); | |
} | |
} |
Seguramente hay otras formas de eliminar la duplicación. Podemos por ejemplo crear un MailBuilderString con la responsabilidad de armar un mail y usar este objeto dentro de cada uno de los MailBuilders para cada ContactInformation.
saludos!