병렬 상속 계층 구조 방지
-
22-08-2019 - |
문제
두 개의 병렬 상속 체인이 있습니다.
Vehicle <- Car
<- Truck <- etc.
VehicleXMLFormatter <- CarXMLFormatter
<- TruckXMLFormatter <- etc.
내 경험에 따르면 병렬 상속 계층 구조는 성장함에 따라 유지 관리에 골칫거리가 될 수 있습니다.
즉.추가하지 않음 toXML(), toSoap(), toYAML()
내 주요 수업에 대한 방법.
관심사 분리 개념을 깨지 않고 병렬 상속 계층 구조를 어떻게 피할 수 있나요?
해결책
나는 방문자 패턴을 사용하려고 생각하고 있습니다.
public class Car : Vehicle
{
public void Accept( IVehicleFormatter v )
{
v.Visit (this);
}
}
public class Truck : Vehicle
{
public void Accept( IVehicleFormatter v )
{
v.Visit (this);
}
}
public interface IVehicleFormatter
{
public void Visit( Car c );
public void Visit( Truck t );
}
public class VehicleXmlFormatter : IVehicleFormatter
{
}
public class VehicleSoapFormatter : IVehicleFormatter
{
}
이를 통해 여분의 상속 트리를 피하고 서식 로직을 차량 클래스에서 분리합니다. Offcourse, 새 차량을 만들 때 Formatter 인터페이스에 다른 방법을 추가해야합니다 (Formatter 인터페이스의 모든 구현 에서이 새로운 방법을 구현).
그러나 나는 이것이 새로운 차량 클래스를 만드는 것이 더 좋다고 생각하며, 당신이 가진 모든 ivehicleformatter에 대해이 새로운 종류의 차량을 처리 할 수있는 새로운 클래스를 만듭니다.
다른 팁
또 다른 접근법은 풀 모델이 아닌 푸시 모델을 채택하는 것입니다. 일반적으로 캡슐화를 깨고 다음과 같은 것을 가지고 있기 때문에 다른 형식 반포가 필요합니다.
class TruckXMLFormatter implements VehicleXMLFormatter {
public void format (XMLStream xml, Vehicle vehicle) {
Truck truck = (Truck)vehicle;
xml.beginElement("truck", NS).
attribute("name", truck.getName()).
attribute("cost", truck.getCost()).
endElement();
...
특정 유형에서 Formatter로 데이터를 가져 오는 곳.
대신, 형식 공유 데이터 싱크를 생성하고 흐름을 반전시켜 특정 유형이 데이터를 싱크로 밀어 넣습니다.
class Truck implements Vehicle {
public DataSink inspect ( DataSink out ) {
if ( out.begin("truck", this) ) {
// begin returns boolean to let the sink ignore this object
// allowing for cyclic graphs.
out.property("name", name).
property("cost", cost).
end(this);
}
return out;
}
...
즉, 여전히 데이터를 캡슐화했으며 태그가 지정된 데이터를 싱크대에 공급하는 것입니다. 그런 다음 XML 싱크는 데이터의 특정 부분을 무시하고 일부를 재주문하고 XML을 작성할 수 있습니다. 내부적으로 다른 싱크 전략을 위임 할 수도 있습니다. 그러나 싱크대는 반드시 차량의 유형에 대해 신경을 쓸 필요는 없으며 일부 형식으로 데이터를 표현하는 방법 만 있습니다. 인라인 문자열 대신 인턴 된 글로벌 ID를 사용하면 계산 비용을 낮추는 데 도움이됩니다 (ASN.1 또는 기타 타이트 형식을 작성하는 경우에만 문제가 있습니다).
포맷터에 대한 상속을 피하려고 할 수 있습니다.간단히 VehicleXmlFormatter
그건 처리할 수 있어 Car
에스, Truck
에, ...재사용은 메소드 간의 책임을 나누고 좋은 파견 전략을 파악함으로써 달성하기 쉬워야 합니다.마법의 과부하를 피하십시오.포맷터의 이름 지정 방법은 최대한 구체적으로 지정하세요(예: formatTruck(Truck ...)
대신에 format(Truck ...)
).
이중 파견이 필요한 경우에만 방문자를 사용하십시오.유형의 객체가 있을 때 Vehicle
실제 구체적인 유형을 모르고 XML로 형식을 지정하려고 합니다.방문자 자체는 포맷터에서 재사용을 달성하는 기본 문제를 해결하지 못하며 필요하지 않은 추가 복잡성을 초래할 수 있습니다.메서드별 재사용(잘라내기 및 디스패치)에 대한 위의 규칙은 방문자 구현에도 적용됩니다.
당신이 사용할 수있는 Bridge_pattern
브리지 패턴은 구현에서 추상화를 해체하여 둘이 독립적으로 다를 수 있습니다..
두 개의 직교 클래스 계층 (the 추출 계층 및 구현 계층 구조)는 구성을 사용하여 연결됩니다 (상속이 아님).이 구성은 두 계층 모두 독립적으로 변화하는 데 도움이됩니다.
구현은 결코 참조하지 않습니다 추출. 추상화에는 포함됩니다 구현 구성원으로서의 인터페이스 (구성을 통해).
예로 돌아 오는 것 :
Vehicle
~이다 추출
Car
그리고 Truck
~이다 정교함
Formatter
~이다 구현 자
XMLFormatter
, POJOFormatter
~이다 콘크리트 이식기
의사 코드 :
Formatter formatter = new XMLFormatter();
Vehicle vehicle = new Car(formatter);
vehicle.applyFormat();
formatter = new XMLFormatter();
vehicle = new Truck(formatter);
vehicle.applyFormat();
formatter = new POJOFormatter();
vehicle = new Truck(formatter);
vehicle.applyFormat();
관련 게시물 :
ixmlformatter를 toxml (), tosoap ()와 함께 yaml () 방법으로 인터페이스로 만들고 차량, 자동차 및 트럭을 모두 구현하십시오. 그 접근법에 무슨 문제가 있습니까?
프레드릭스 대답에 제네릭을 추가하고 싶습니다.
public class Car extends Vehicle
{
public void Accept( VehicleFormatter v )
{
v.Visit (this);
}
}
public class Truck extends Vehicle
{
public void Accept( VehicleFormatter v )
{
v.Visit (this);
}
}
public interface VehicleFormatter<T extends Vehicle>
{
public void Visit( T v );
}
public class CarXmlFormatter implements VehicleFormatter<Car>
{
//TODO: implementation
}
public class TruckXmlFormatter implements VehicleFormatter<Truck>
{
//TODO: implementation
}