接口(4):接口中的域、嵌套接口、接口与工厂

迈不过友情╰ 2023-07-04 06:26 174阅读 0赞

一、接口中的域

  1. 因为你放入接口中的任何域都自动是staticfinal的,所以接口就成为了一种很便捷的用来创建常量组的工具。在java SE5之前,这是产生于CC++中的enum(枚举类型)具有相同效果的类型的唯一途径。因此在java SE5之前的代码中你会看到下面这样的代码:
  2. public interface Months {
  3. int JANUARY = 1, FEBRUARY = 2, MARCH = 3, APRIL = 4, MAY = 5, JUNE = 6, JULY = 7, AUGUST = 8, SEPTEMBER = 9,
  4. OCTOBER = 10, NOVEMBER = 11, DECEMBER = 12;
  5. }
  6. 请注意,java中标识具有常量初始化值的static final时,会使用大写字母的风格(在一个标识符中用下划线来分隔多个单词)。接口中的域自动是public的,所以没有显式地指明这一点。
  7. 有了java SE5,你就可以使用更加强大而灵活的enum关键字,因此,使用接口来群组常量已经显得没有什么意义了。

二、初始化接口中的域

  1. 在接口中定义的域不能是“空final”,但是可以被非常量表达式初始化。例如:
  2. import java.util.Random;
  3. public interface RandVals {
  4. Random r = new Random();
  5. int RANDOM_INT = r.nextInt(10);
  6. long RANDOM_LONG = r.nextLong() * 10;
  7. float RANDOM_FLOAT = r.nextFloat() * 10;
  8. double RANDOM_DOUBLE = r.nextDouble() * 10;
  9. }
  10. 既然域是static的,它们就可以在类第一次被加载时初始化,这发生在任何域首次被访问时,这里给出一个简单的测试:
  11. public class TestRandVals {
  12. public static void main(String[] args) {
  13. System.out.println(RandVals.RANDOM_INT);
  14. System.out.println(RandVals.RANDOM_LONG);
  15. System.out.println(RandVals.RANDOM_FLOAT);
  16. System.out.println(RandVals.RANDOM_DOUBLE);
  17. }
  18. }
  19. 当然,这些域不是接口的一部分,它们的值被存储在该接口的静态存储区域内。

三、嵌套接口

  1. 接口可以嵌套在类或其他接口中。这揭示了许多非常有趣的特性:
  2. class A {
  3. interface B {
  4. void f();
  5. }
  6. public class BImpl implements B {
  7. @Override
  8. public void f() {
  9. }
  10. }
  11. private class BImpl2 implements B {
  12. @Override
  13. public void f() {
  14. }
  15. }
  16. public interface C {
  17. void f();
  18. }
  19. class CImpl implements C {
  20. @Override
  21. public void f() {
  22. }
  23. }
  24. private class CImpl2 implements C {
  25. @Override
  26. public void f() {
  27. }
  28. }
  29. private interface D {
  30. void f();
  31. }
  32. private class DImpl implements D {
  33. @Override
  34. public void f() {
  35. }
  36. }
  37. public class DImpl2 implements D {
  38. @Override
  39. public void f() {
  40. }
  41. }
  42. public D getD() {
  43. return new DImpl2();
  44. }
  45. private D dRef;
  46. public void receiveD(D d) {
  47. dRef = d;
  48. dRef.f();
  49. }
  50. }
  51. interface E {
  52. interface G {
  53. void f();
  54. }
  55. public interface H {
  56. void f();
  57. }
  58. void g();
  59. // The interface member type I can only be public
  60. // private interface I {}
  61. }
  62. /**
  63. * 嵌套接口
  64. */
  65. public class NestingInterfaces {
  66. public class BImpl implements A.B {
  67. @Override
  68. public void f() {
  69. }
  70. }
  71. class CImpl implements A.C {
  72. @Override
  73. public void f() {
  74. }
  75. }
  76. // class DImpl implements A.D{
  77. // @Override
  78. // public void f() {
  79. // }
  80. // }
  81. class EImpl implements E {
  82. @Override
  83. public void g() {
  84. }
  85. }
  86. class EGImpl implements E.G {
  87. @Override
  88. public void f() {
  89. }
  90. }
  91. class EImpl2 implements E {
  92. @Override
  93. public void g() {
  94. }
  95. class EG implements E.G {
  96. @Override
  97. public void f() {
  98. }
  99. }
  100. }
  101. public static void main(String[] args) {
  102. A a = new A();
  103. // A.D ad=a.getD();
  104. // A.DImpl2 di2 = a.getD();
  105. // a.getD().f();
  106. A a2 = new A();
  107. a2.receiveD(a.getD());
  108. }
  109. }
  110. 在类中嵌套接口的语法是相当显而易见的,就像非嵌套接口一样,可以拥有public和“包访问”两种可视性。
  111. 作为一种新添加的方式,接口也可以被实现为private的,就像在A.D中所看到的(相同的语法既适用于嵌套接口,也适用于嵌套类)。那么private的嵌套接口能带来什么好处呢?你可能会猜想,它只能够被实现为DImpl中的一个private内部类,但是A.DImpl2展示了它同样可以被实现为public类。但是,A.DIml2只能被其自身所使用。你无法说它实现了一个private接口D。因此,实现一个private接口只是一种方式,它可以强制该接口中的方法定义不要添加任何类型信息(也就是说,不允许向上转型)。
  112. getD()方法使我们陷入了一个进退两难的境地,这个问题与private接口相关:它是一个返回对private接口的引用的public方法。你对这个方法的返回值能做些什么呢?在main()中,可以看到数次尝试使用返回值的行为都失败了。只有一种方式可成功,那就是将返回值交给有权使用它的对象。在本例中,是另一个A通过receiveD()方法来实现的。
  113. 接口E说明接口彼此之间也可以嵌套。然而,作用于接口的各种规则,特别是所有的接口元素都必须是public的,在此都会被严格执行。因此,嵌套在另一个接口中的接口自动就是public的,而不能声明为private
  114. NestingInterfaces展示了嵌套接口的各种实现方式。特别要注意的是,当实现某个接口时,并不需要实现嵌套在其内部的任何接口。而且,private接口不能在定义它的类之外被实现。
  115. 添加这些特性的最初原因可能是出于对严格的语法一致性的考虑,但是我总认为,一旦你了解了某种特性,就总能够找到它的用武之地。

四、接口与工厂

  1. 接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将生成接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这就使得我们可以透明地将某个实现替换为另一个实现。下面的实例展示了工厂方法的结构:
  2. interface Service {
  3. void method1();
  4. void method2();
  5. }
  6. interface ServiceFactory {
  7. Service getService();
  8. }
  9. class Implementation1 implements Service {
  10. Implementation1() {
  11. }
  12. @Override
  13. public void method1() {
  14. System.out.println("Implementation1 method1");
  15. }
  16. @Override
  17. public void method2() {
  18. System.out.println("Implementation1 method2");
  19. }
  20. }
  21. class Implementation1Factory implements ServiceFactory {
  22. @Override
  23. public Service getService() {
  24. return new Implementation1();
  25. }
  26. }
  27. class Implementation2 implements Service {
  28. Implementation2() {
  29. }
  30. @Override
  31. public void method1() {
  32. System.out.println("Implementation2 method1");
  33. }
  34. @Override
  35. public void method2() {
  36. System.out.println("Implementation2 method2");
  37. }
  38. }
  39. class Implementation2Factory implements ServiceFactory {
  40. @Override
  41. public Service getService() {
  42. return new Implementation2();
  43. }
  44. }
  45. public class Factories {
  46. public static void serviceConsumer(ServiceFactory fact) {
  47. Service s = fact.getService();
  48. s.method1();
  49. s.method2();
  50. }
  51. public static void main(String[] args) {
  52. serviceConsumer(new Implementation1Factory());
  53. serviceConsumer(new Implementation2Factory());
  54. }
  55. }
  56. 如果不是用工厂方法,你的代码就必须在某处指定将要创建的Service的确切类型,以便调用合适的构造器。
  57. 为什么我们想要添加这种额外级别的间接性呢?一个常见的原因是想要创建框架:假设你正在创建一个对弈游戏系统,例如,在相同的棋盘上下国际象棋和西洋跳棋:
  58. interface Game {
  59. boolean move();
  60. }
  61. interface GameFactory {
  62. Game getGame();
  63. }
  64. /**
  65. * 西洋跳棋
  66. */
  67. class Checkers implements Game {
  68. private int moves = 0;
  69. private static final int MOVES = 3;
  70. @Override
  71. public boolean move() {
  72. System.out.println("Checkers move " + moves);
  73. return ++moves != MOVES;
  74. }
  75. }
  76. class CheckersFactory implements GameFactory {
  77. @Override
  78. public Game getGame() {
  79. return new Checkers();
  80. }
  81. }
  82. /**
  83. * 国际象棋
  84. */
  85. class Chess implements Game {
  86. private int moves = 0;
  87. private static final int MOVES = 4;
  88. @Override
  89. public boolean move() {
  90. System.out.println("Chess move " + moves);
  91. return ++moves != MOVES;
  92. }
  93. }
  94. class ChessFactory implements GameFactory {
  95. @Override
  96. public Game getGame() {
  97. return new Chess();
  98. }
  99. }
  100. public class Games {
  101. public static void playGame(GameFactory factory) {
  102. Game s = factory.getGame();
  103. while (s.move())
  104. ;
  105. }
  106. public static void main(String[] args) {
  107. playGame(new CheckersFactory());
  108. playGame(new ChessFactory());
  109. }
  110. }
  111. 如果Games类表示一段复杂的代码,那么这种方式就允许你在不同类型的游戏中复用这段代码。你可以在想象一些能够从这个模式中受益的更加精巧的游戏。

五、总结

  1. “确定接口是理想选择,因而应该总是选择接口而不是具体的类。”这其实是一种引诱。当然,对于创建类,几乎在任何时刻,都可以替代为创建一个接口和一个工厂。
  2. 许多人都掉进了这种诱惑的陷阱,只要有可能就去创建接口和工厂。这种逻辑看起来好像是因为需要使用不同的具体实现,因此总是应该添加这种抽象性。这实际上已经变成了一种草率的设计优化。
  3. 任何抽象性都应该是应真正的需求而产生的。当必需时,你应该重构接口而不是到处添加额外级别的间接性,并由此带来的额外的复杂性。这种额外的复杂性非常显著,如果你让某人去处理这种复杂性,只是因为你意识到由于以防万一而添加了新接口,而没有其他更有说服力的原因,那么好吧,如果我碰上了这种事,那么就会质疑此人所作的所有设计了。
  4. 恰当的原则应该是优先选择类而不是接口。从类开始,如果接口的必需性变得非常明确,那么就进行重构。接口是一种重要的工具,但是它们容易被滥用。

如果本文对您有很大的帮助,还请点赞关注一下。

发表评论

表情:
评论列表 (有 0 条评论,174人围观)

还没有评论,来说两句吧...

相关阅读