기본적으로 우리는 현명하게 행동하기를 원합니다.
다음 문제를 고려하십시오.
직사각형 그룹이 주어졌으며 면적을 10 % 증가시키고 싶습니다. 그래서 내가하는 일은 사각형의 길이를 이전보다 1.1 배로 설정하는 것입니다.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
rectangle.Length = rectangle.Length * 1.1;
}
}
이제이 경우 모든 직사각형의 길이가 10 % 증가하여 면적이 10 % 증가합니다. 불행히도 누군가가 실제로 사각형과 사각형의 혼합물을 전달했으며 사각형의 길이가 변경되면 너비도 바뀌 었습니다.
모든 단위 테스트를 작성하여 사각형 모음을 사용했기 때문에 단위 테스트가 통과되었습니다. 이제 몇 달 동안 눈에 띄지 않을 수있는 미묘한 버그를 응용 프로그램에 도입했습니다.
더 나쁜 것은 회계에서 Jim이 내 방법을보고 내 방법에 사각형을 전달하면 크기가 21 % 증가한다는 사실을 사용하는 다른 코드를 작성하는 것입니다. 짐은 행복하고 더 현명한 사람은 없습니다.
Jim은 다른 부서의 우수한 업무를 위해 승진했습니다. Alfred는 주니어로 회사에 합류했습니다. 그의 첫 번째 버그 보고서에서 Jill from Advertising은이 방법으로 제곱을 전달하면 21 % 증가하고 버그 수정을 원한다고보고했습니다. Alfred는 Squares와 Rectangles가 코드의 어느 곳에서나 사용되며 상속 체인을 깨는 것은 불가능하다는 것을 알고 있습니다. 또한 회계 소스 코드에 액세스 할 수 없습니다. 따라서 Alfred는 다음과 같이 버그를 수정합니다.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle)
{
rectangle.Length = rectangle.Length * 1.1;
}
if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Alfred는 자신의 동네 짱 해킹 기술에 만족하고 질은 버그가 수정되었음을 서명합니다.
다음 달 회계는 IncreaseRectangleSizeByTenPercent
방법에 제곱을 전달 하고 면적이 21 % 증가하는 데 의존했기 때문에 아무도 돈을받지 못했습니다 . 회사 전체가 "우선 순위 1 버그 수정"모드로 들어가 문제의 원인을 추적합니다. 그들은 Alfred의 수정에 대한 문제를 추적합니다. 그들은 회계와 광고를 모두 행복하게 유지해야한다는 것을 알고 있습니다. 따라서 메소드 호출로 사용자를 식별하여 문제를 해결합니다.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
IncreaseRectangleSizeByTenPercent(
rectangles,
new User() { Department = Department.Accounting });
}
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
{
rectangle.Length = rectangle.Length * 1.1;
}
else if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
그리고 등등.
이 일화는 프로그래머가 매일 직면하는 실제 상황을 기반으로합니다. Liskov 교체 원칙의 위반은 위반 사항을 수정하는 일의 무리를 끊고되는 시간은 작성하고 이후 만 년을 발탁 매우 미묘한 버그 도입 할 수 고정되지 는 가장 큰 클라이언트를 화나게됩니다.
이 문제를 해결하는 두 가지 현실적인 방법이 있습니다.
첫 번째 방법은 Rectangle을 변경할 수 없게 만드는 것입니다. Rectangle 사용자가 Length 및 Width 속성을 변경할 수 없으면이 문제는 사라집니다. 길이와 너비가 다른 사각형을 원하면 새 사각형을 만듭니다. 사각형은 사각형에서 행복하게 상속받을 수 있습니다.
두 번째 방법은 사각형과 사각형 사이의 상속 체인을 끊는 것입니다. 사각형이 단일 SideLength
속성 을 갖는 것으로 정의 되고 사각형에 Length
and Width
속성이 있고 상속이 없으면 사각형을 기대하고 사각형을 가져 와서 실수로 물건을 깰 수 없습니다. C # 용어로 seal
사각형 클래스 를 사용할 수 있습니다. 이렇게하면 모든 사각형이 실제로 사각형이됩니다.
이 경우 문제를 해결하는 "불변의 객체"방식이 마음에 듭니다. 사각형의 정체성은 길이와 너비입니다. 객체의 아이덴티티를 변경하려고 할 때 실제로 원하는 것은 새로운 객체 라는 것이 합리적입니다 . 기존 고객을 잃고 새로운 고객을 얻는 경우 기존 고객에서 새 고객으로 Customer.Id
필드를 변경하지 않고 새 고객 을 만듭니다 Customer
.
Liskov 대체 원칙의 위반은 실제 세계에서 일반적입니다. 대부분 많은 코드가 능력이 없거나 시간이 지남에 따라 / 관리 / 실수하지 않는 사람들이 작성했기 때문입니다. 그것은 매우 불쾌한 문제로 이어질 수 있습니다. 대부분의 경우 상속 대신 구성 을 선호 합니다.