我正在阅读清洁建筑-鲍勃·马丁。他谈到了通过接口打破依赖关系。例如。 B类使用A类。因此,B类依赖于A(B ---> A)类。我们可以添加一个接口,使B依赖于该接口,也可以使A依赖于该接口(A ---> I <---- B)。
我不理解的是,如果类A具有在B需要的函数中使用的私有成员变量。我不必重写B中与A相同的代码吗?另外,这不就是重复的代码吗?
这里是一个例子。
class Car {
private String color;
private Integer numberOfTires;
[...]
public void printCar()
{
System.out.print("Color: " + color);
System.out.print("Number of tires: " + numberOfTires);
}
}
class Inventory{
private Car car;
private Truck truck; // left out for brevity
public void printCar()
{
car.printCar();
}
public void printTruck()
{
truck.printTruck();
}
}
我看不到接口如何帮助解决这种依赖性。
答案 0 :(得分:1)
@Kaue Silveira提供了一个很好的例子,但没有提及另一个重要方面。
通常,接口的重点是减少类。 我想您熟悉耦合和内聚的术语。 您始终希望拥有松散耦合且高度内聚的代码。 意思是,您不希望类彼此依赖(耦合),而是希望以继承,多态等形式共享某些逻辑。这些概念是高质量面向对象设计的一些基本支柱。如果您不熟悉这些主题,那么绝对值得一读。
回到这个问题,如果要处理具有很多特殊情况的复杂逻辑,请确保通常会重复一些。但是,此类问题与仅旨在遵守DRY原理并以通用方式解决方案的方式解决情况的其他设计模式和原理有关。
接口背后的主要思想是为类设置逻辑结构,从而有助于对象操作的统一性。
想象一个证券交易所系统。 该接口只有一个名为execute的方法,它将执行一些应用程序逻辑。
public interface Order{
void execute();
}
可能实现此接口的其他类可能是Buy and Sell。看起来像这样:
public class Buy implement Order{
@Override
public void execute(){
//TODO: Some logic
}
}
现在,买卖双方将具有相似的代码,甚至可能有一些重复的代码,但是更重要的是,当它们实现相同的接口时,您可以以统一的方式处理它们。您可以具有一些StockManager类,该类会将“买入”订单和“卖出”订单同时放置在某些Quee<Order>
中。由此可以得出结论,只要使用此类Quee,您就可以在Order接口的任何实现上调用execute()
方法。
在上一个参数的基础上,通过使用接口和诸如Spring之类的框架,您可以进行自动装配。这显着减少了对低级实现类的依赖,使您可以更改低级类而不影响顶级处理程序。这种类型的应用程序设计是SOA(面向服务的体系结构)中的常见做法。
答案 1 :(得分:0)
生产环境B中的运行时将使用真实的类A(或实现相同接口的另一个类),因此无需重复代码。
使用接口,您可以将另一种实现方式用于其他用途,例如,对类B的单元测试使用轻量级的内存伪类A,从而避免使用重量级的真实类A。
您(只能)打破“静态的构建时间”依赖性。始终保持“动态,运行时”依赖性。
在代码中:
public interface AInterface {}
public class A implements AInterface {}
public class B {
AInterface a;
public B(AInterface a) {
this.a = a;
}
}
public class Main {
B b = B(A());
}
public class AFake implements AInterface {}
public class BTest {
B b = B(AFake())
}
此示例使用构造函数依赖注入: https://en.wikipedia.org/wiki/Dependency_injection#Constructor_injection_comparison。
答案 2 :(得分:0)
让我们说我们有这样的“自行车和交通”课程:
class Bike{
public void ride(){
System.out.println("Riding from point A to B");
}
}
class Transportation{
public void commute(Bike b){
b.ride();
}
}
现在,这里的运输课程与自行车紧密相关。即使有其他交通工具也不能上下班。另外,Bike类的任何更改都会直接影响运输的行为。
不是上面的代码,而是说有一个名为Vehicle的接口,如下所示:
public interface Vehicle{
void move();
}
class Bike implements Vehicle{
public void move(){
System.out.println("Riding on Bike");
}
}
class Transportation{
public void commute(Vehicle v){
v.move();
}
}
Here Vehicle接口保证无论什么类实现它,无论哪种情况,它都将具有具有完全相同签名的move()方法。而且,commute()方法现在带有一个Vehicle对象。这样就可以稍后使用Vehicle类的另一个实现来创建Transportation类对象,而无需甚至不知道Transportation类。例如:
class Car implements Vehicle{
public void move(){
System.out.println("Moving in car");
}
}
Car c = new Car();
new Transportation(c).commute();
Bike b = new Bike();
new Transportation(b).commute();
使用Vehicle接口已经从具体的类中删除了Transportation的依赖。同样,任何需要使用Transportation的类都必须使用方法move()来实现Vehicle接口。从而在任何车辆和运输工具之间建立合同。
答案 3 :(得分:0)
假设您的Inventory
是“车辆清单”,而不是“一辆车和一辆卡车的清单”。
考虑到这一点,可能会有所帮助:
Car
是 Vehicle
Truck
是 Vehicle
Inventory
取决于 Vehicle
-不是基于Car
或Truck
,它对此一无所知类型Vehicle::printDetails
由Car
和Truck
。
public class Scratch4 {
public static void main(String args[]) throws Exception {
Car car = new Car("Blue", 4);
Truck truck = new Truck();
Inventory inventory = new Inventory();
inventory.addVehicle(car);
inventory.addVehicle(truck);
inventory.printVehicleDetails();
}
}
interface Vehicle {
void printDetails();
}
class Car implements Vehicle {
private String color;
private Integer numberOfTires;
public Car(String color, Integer numberOfTires) {
this.color = color;
this.numberOfTires = numberOfTires;
}
public void printDetails() {
System.out.println("Color: " + color);
System.out.println("Number of tires: " + numberOfTires);
System.out.println();
}
}
class Truck implements Vehicle {
@Override
public void printDetails() {
System.out.println("Some kind of truck");
System.out.println();
}
}
class Inventory {
private List<Vehicle> vehicles = new ArrayList<>();;
public void addVehicle(Vehicle vehicle) {
vehicles.add(vehicle);
}
public void printVehicleDetails() {
vehicles.forEach(Vehicle::printDetails);
}
}
收益
Color: Blue
Number of tires: 4
Some kind of truck
答案 4 :(得分:0)
Robert谈到打破依赖关系,当然您甚至可以打破Inventory
和Car
之间的依赖关系,但是我认为这并没有给您带来太多好处,因为您不会跨过架构边界。
最好打破对System.out
的静态依赖,因为IO是体系结构边界,然后您可以替换打印Car
或Inventory
的方式,这非常对于测试很有用。
打破静态依赖关系
class Car {
private String color;
private Integer numberOfTires;
private PrintStream output = System.out;
void setOutput(PrintStream output){
this.output = Objects.requireNotNull(output);
}
public void printCar() {
output.print("Color: " + color);
output.print("Number of tires: " + numberOfTires);
}
}
现在您可以在测试中替换输出以捕获输出结果。
应用接口隔离原则
public interface Output {
public void print(String msg);
}
class Car {
private String color;
private Integer numberOfTires;
private Output output = (msg) -> System.out.println(msg);
void setOutput(Output output){
this.output = Objects.requireNotNull(output);
}
public void printCar() {
output.print("Color: " + color);
output.print("Number of tires: " + numberOfTires);
}
}
现在,您唯一的依赖项是Output
接口,它在测试中更容易替换或模拟。
这个小小的变化使您的Car
独立于具体的输出系统,或者像罗伯特(Robert)所说的a detail
。我也可以想象实现一个JTextAreaOutput
,以便可以在GUI中显示输出。
干净的体系结构告诉我们IO是一个细节,我们的业务代码不应依赖于它。看来Car
和Inventory
是您的业务代码,因此我向您展示了如何将其与具体的输出系统(细节)分离。
+-----+ uses +--------+ implements +--------------+
| Car | --------> | Output | <------------- | SystemOutput |
+-----+ +--------+ +--------------+
---------> control flow ------------->
我们还应用了Dependency Inversion
原理,因为源代码依赖性指向控制流。