고체: 리스코프 교체 원리
리스코프 교체 원칙은 모든 원칙 중 가장 기술적인 원칙이다.그러나 이것은 결합 해제 응용 프로그램을 개발하는 데 가장 도움이 되는 응용 프로그램으로, 이것은 다시 사용할 수 있는 구성 요소를 설계하는 기초이다.
이 원칙에 대한 Barbara Liskov의 정의는 다음과 같습니다.
Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T”
Liskov가 제시한 정의는 Bertrand Meyer의 정의를 바탕으로 하는 계약 설계(DbC)입니다.선결 조건, 불변량 및 후결 조건을 통해 확정된 계약:
1996년에 Robert C.Martin은 Liskov가 제시한 개념을 다음과 같이 재정의했다.
Function that use pointers of references to base classes must be able to use objects of derived classes without knowing it.
Bob Martin이 제시한 재정의는 Liskov가 수년 전에 실현한 개념을 간소화하고 개발자가 이를 채택하는 데 도움이 된다.
리스코프 대체 원칙을 어기다
은행 실체의 개발자로서 당신은 은행 계좌를 관리하는 시스템을 실현해야 합니다.당신의 사장은 프로젝트의 첫 번째 돌진 단계에서 기본과 고급 은행 계좌를 관리하는 시스템을 실시할 것을 요구합니다.그것들 사이의 차이는 후자가 어떤 예금에 대한 선호점을 축적했다는 데 있다.
다음과 같은 추상류를 실현하는 것을 체계적인 기초로 한다.
public abstract class BankAccount {
/**
* In charge of depositing a specific amount into the account.
* @param amount Dollar ammount.
*/
public abstract void deposit(double amount);
/**
* In charge of withdrawing a specific amount from the account.
* @param amount Dollar amount.
* @return Boolean result.
*/
public abstract boolean withdraw(double amount);
}
이 추상 클래스는 모든 파생 클래스가 BankAccount 클래스에서 정의한 추상 방법을 다시 쓰는 의무를 정의합니다.이것은 기본 계좌와 보험료 계좌가 반드시 예금과 인출 방식보다 우선해야 한다는 것을 의미한다.public class BasicAccount extends BankAccount {
private double balance;
@Override
public void deposit(double amount) {
this.balance += amount;
}
@Override
public boolean withdraw(double amount) {
if(this.balance < amount)
return false;
else{
this.balance -= amount;
return true;
}
}
}
public class PremiumAccount extends BankAccount {
private double balance;
private int preferencePoints;
@Override
public void deposit(double amount) {
this.balance += amount;
accumulatePreferencePoints();
}
@Override
public boolean withdraw(double amount) {
if(this.balance < amount)
return false;
else{
this.balance -= amount;
accumulatePreferencePoints();
return true;
}
}
private void accumulatePreferencePoints(){
this.preferencePoints++;
}
}
이러한 클래스 중 어느 것이든 생산 환경에 대한 최소한의 검증을 고려하십시오.모든 기본과 고급 계좌의 관리 비용은 매년 25달러의 할인을 받는다.이 정책을 구현하려면 다음 클래스를 정의합니다.
public class WithdrawalService {
public static final double ADMINISTRATIVE_EXPENSES_CHARGE = 25.00;
public void cargarDebitarCuentas(){
BankAccount basiAcct = new BasicAccount();
basiAcct.deposit(100.00);
BankAccount premiumAcct = new PremiumAccount();
premiumAcct.deposit(200.00);
List<BankAccount> accounts = new ArrayList();
accounts.add(basiAcct);
accounts.add(premiumAcct);
debitAdministrativeExpenses(accounts);
}
private void debitAdministrativeExpenses(List<BankAccount> accounts){
accounts.stream()
.forEach(account -> account.withdraw(WithdrawalService.ADMINISTRATIVE_EXPENSES_CHARGE));
}
}
프로젝트의 두 번째 돌진 단계에서 사장은 너에게 은행 계좌 관리 시스템에서 장기 계좌를 실현하라고 요구한다.장기 계좌와 기본/프리미엄 계좌 간의 차이는 다음과 같다.public class LongTermAccount extends BankAccount {
private double balance;
@Override
public void deposit(double amount) {
this.balance += amount;
}
@Override
public boolean withdraw(double amount) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
이 부분은 리스코프 대체 원칙을 분명히 위반했다.인출 방법을 다시 쓰지 않으면 LongerAccount의 BankAccount 클래스를 확장할 수 없습니다.그러나 장기 계좌는 항목의 요구에 따라 인출을 허용하지 않는다.다음 두 가지 옵션으로 이 문제를 해결할 수 있습니다.
private void debitAdministrativeExpenses(List<BankAccount> accounts){
for(BankAccount account : accounts){
if(account instanceof LongTermAccount)
continue;
else
account.withdraw(ADMINISTRATIVE_EXPENSES_CHARGE);
}
}
Liskov 교체 원리 구현
은행 계좌 구조의 주요 문제는 장기 계좌는 일반적인 은행 계좌가 아니라 적어도 뱅크 어카운트 추상류에서 정의된 유형이 아니라는 것이다.소인 추리 영역에는 하나의 종류가 'X' 유형의 하위 유형인지 검사하는 간단한 테스트가 있다.오리 테스트에 따르면 "오리처럼 보이고 수영은 오리처럼 보이며
코드가 LSP와 일치하도록 다음과 같이 변경합니다.
public abstract class BankAccount {
/**
* In charge of depositing a specific amount into the account.
* @param amount Dollar ammount.
*/
public abstract void deposit(double amount);
}
추상적인 인출 계좌류는 인출 방법을 정의할 것이다.public abstract class WithdrawableAccount extends BankAccount {
/**
* In charge of withdrawing a specific amount from the account.
* @param amount Dollar amount.
* @return Boolean result.
*/
public abstract boolean withdraw(double amount);
}
기본 및 고급 계정 클래스는 BankAccount 클래스를 확장하는 인출 계정 클래스를 확장합니다.이런 플러그인 상속은 기본/고급 계좌가 예금과 인출의 두 가지 방법을 동시에 가지도록 허용한다.public class BasicAccount extends WithdrawableAccount {
private double balance;
@Override
public void deposit(double amount) {
this.balance += amount;
}
@Override
public boolean withdraw(double monto) {
if(this.balance < monto)
return false;
else{
this.balance -= monto;
return true;
}
}
}
public class PremiumAccount extends WithdrawableAccount {
private double balance;
private int preferencePoints;
@Override
public void deposit(double monto) {
this.balance += monto;
accumulatePreferencePoints();
}
@Override
public boolean withdraw(double monto) {
if(this.balance < monto)
return false;
else{
this.balance -= monto;
accumulatePreferencePoints();
return true;
}
}
private void accumulatePreferencePoints(){
this.preferencePoints++;
}
}
인출 서비스 유형은 인출 계좌 유형이나 하위 유형으로만 이루어진다.public class WithdrawableService {
public static final double ADMINISTRATIVE_EXPENSES_CHARGE = 25.00;
public void cargarDebitarCuentas(){
WithdrawableAccount basicAcct = new BasicAccount();
basicAcct.deposit(100.00);
WithdrawableAccount premiumAcct = new PremiumAccount();
premiumAcct.deposit(200.00);
List<WithdrawableAccount> accounts = new ArrayList();
accounts.add(basicAcct);
accounts.add(premiumAcct);
debitarGastosAdmon(accounts);
}
private void debitarGastosAdmon(List<WithdrawableAccount> accounts){
accounts.stream()
.forEach(account -> account.withdraw(WithdrawableService.ADMINISTRATIVE_EXPENSES_CHARGE));
}
}
클래스 구조를 변경하면 코드가 LSP와 일치하는지 확인합니다.이제 LongterAccount 클래스에서 추출 방법을 사용할 필요가 없습니다.그 밖에 우리는 인출 계좌의 대상과 이 추상적인 종류의 모든 하위 유형을 교환할 것이다.클래스 구조도 OCP가 호환됩니다. 만약에 우리가 인출을 허용하지 않는 다른 은행 계좌 유형을 추가하면 현재 코드를 수정하여 은행 계좌 유형을 확장할 필요가 없습니다.대체 원리의 중요성
LSP를 사용하면 설계 단계에서 오류의 범위를 파악하고 수정할 수 있습니다.Liskov 교체 원칙은 자바 기업판과 스프링 프레임워크에서 널리 사용되는 의존 주입 개념을 개발하는 기초이다.
Liskov 대체 원칙을 위반하는 경우를 쉽게 발견하려면 다음 프롬프트를 사용합니다.
객체의 유형 도입 조건을 사용하는 경우 위의 instanceof 예와 같이 LSP 충돌이 발생합니다.
추상 클래스를 확장하고 그 중 하나를 빈 방법으로 설정하거나 정의되지 않은 이상을 던지면 LSP 충돌이 발생합니다.
왜 추상류가 아닌 인터페이스를 사용하지 않습니까
인터페이스와 추상류는 매우 비슷하지만 그것들은 다르다.추상 클래스는 하위 클래스가 반드시 실현해야 하는 기능을 정의한다.다른 한편, 인터페이스는 주어진 인터페이스의 모든 종류를 실현해야 하는 기능을 정의했다.
예를 들어 추상적인 정의에서 말이 나는 것은 말의 일종이기 때문이다.그에 비해 인터페이스는'사물'을'이동할 수 있다'고 정의한다. 인터페이스를 실현함으로써 사물이 반드시 이동해야 한다는 약속이 존재하기 때문이다.
이것은 두 가지 문제를 불러일으켰다.
응, 여기는 정답이 없어.LSP는 파팅 배경에서 정의됩니다.그러나 나는 이것이 더 이상 효과가 없다고 생각한다.LSP는 추상 클래스나 인터페이스가 아니라 계약 이행에 관한 것입니다.
다음 글에서 우리는 인터페이스 격리 원칙을 토론할 것이다.
너는 나를 따라올 수도 있고, 나를 따라올 수도 있다.
Reference
이 문제에 관하여(고체: 리스코프 교체 원리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/victorpinzon1988eng/solid-liskov-substitution-principle-3jel텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)