一、引言

写了3篇有关设计模式的文章了,大家有了些反馈,说能从中学到一些东西,我感到很欣慰,那就继续努力。今天我要写第四个模式了,该模式叫抽象工厂。上一篇文章我们讲了【工厂方法】模式,它是为了解决【简单工厂】模式所面对的问题,它的问题就是:如果我们增加新的产品,工厂类的方法就要修改本身的代码,增加产品越多,其逻辑越复杂,同时这样的修改也是不符合【开放关闭原则OCP】,对修改代码关闭,对增加代码开放。为了解决【简单工厂】的问题,我们引出了【工厂方法】模式,通过子类化工厂类,解决了工厂类责任的划分,产品和相应的工厂一一对应,符合了OCP。如果我们要设计一套房子,当然我们知道房子是由房顶、地板、窗户、房门组成的,别的组件暂时省略,先设计一套古典风格的房子,再创建一套现代风格的房子,再创建一套欧式风格的房子,这么多套房子,我们该怎么办呢?今天我们要讲的【抽象工厂】模式可以很好的解决多套变化的问题。

二、抽象工厂详细介绍

  2.1、动机(Motivate):

在软件系统中,经常面临着"一系统相互依赖的对象"的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种"封装机制"来避免客户程序和这种"多系列具体对象创建工作"的紧耦合?

  2.2、意图(Intent):

    提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。                            ——《设计模式》GoF

  2.3、结构图(Structure)

   

   该图是抽象工厂的UML图,结合抽象工厂的意图、动机和图示来理解该模式,今天我们就以建设房子为例来说明抽象工厂的实现机理。

2.4、模式的组成

可以看出,在抽象工厂模式的结构图有以下角色:

(1)、抽象产品类角色(AbstractProduct):为抽象工厂中相互依赖的每种产品定义抽象接口对象,也可以这样说,有几种产品,就要声明几个抽象角色,每一个抽象产品角色和一种具体的产品相匹配。

(2)、具体产品类(ConcreteProduct):具体产品类实现了抽象产品类,是针对某个具体产品的实现的类型。

(3)、抽象工厂类角色(Abstract Factory):定义了创建一组相互依赖的产品对象的接口操作,每种操作和每种产品一一对应。

(4)、具体工厂类角色(ConcreteFactory):实现抽象类里面的所有抽象接口操作,可以创建某系列具体的产品,这些具体的产品是“抽象产品类角色”的子类。

2.5、抽象工厂的具体代码实现

随着我们年龄的增大,我们也到了结婚的年龄。结婚首要的问题就是房子的问题,假设我有一个很有钱的爸爸,哈哈,有钱可以解决很多问题。作为长子的我,希望能有一套欧式风格的房子,再加上田园风光,此生足矣。我弟弟就不一样了,他想要一套现代样式的房子,如果兄弟姊妹再多年一点,那就有更多的要求了。由于房子由房顶、地板、窗户和房门组成,其他组件暂时省略,有这么多套房子要建设,每套房子的房顶、地板、窗户和房门都是一个体系的,那就让我们看看如何使用【抽象工厂】模式来实现不同房屋的建造。

  1. /// <summary>
  2. /// 下面以不同系列房屋的建造为例子演示抽象工厂模式
  3. /// 因为每个人的喜好不一样,我喜欢欧式的,我弟弟就喜欢现代的
  4. /// 客户端调用
  5. /// </summary>
  6. class Client
  7. {
  8. static void Main(string[] args)
  9. {
  10. // 哥哥的欧式风格的房子
  11. AbstractFactory europeanFactory= new EuropeanFactory();
  12. europeanFactory.CreateRoof().Create();
  13. europeanFactory.CreateFloor().Create();
  14. europeanFactory.CreateWindow().Create();
  15. europeanFactory.CreateDoor().Create();
  16.  
  17. //弟弟的现代风格的房子
  18. AbstractFactory modernizationFactory = new ModernizationFactory();
  19. modernizationFactory.CreateRoof().Create();
  20. modernizationFactory.CreateFloor().Create();
  21. modernizationFactory.CreateWindow().Create();
  22. modernizationFactory.CreateDoor().Create();
  23. Console.Read();
  24. }
  25. }
  26.  
  27. /// <summary>
  28. /// 抽象工厂类,提供创建不同类型房子的接口
  29. /// </summary>
  30. public abstract class AbstractFactory
  31. {
  32. // 抽象工厂提供创建一系列产品的接口,这里作为例子,只给出了房顶、地板、窗户和房门创建接口
  33. public abstract Roof CreateRoof();
  34. public abstract Floor CreateFloor();
  35. public abstract Window CreateWindow();
  36. public abstract Door CreateDoor();
  37. }
  38.  
  39. /// <summary>
  40. /// 欧式风格房子的工厂,负责创建欧式风格的房子
  41. /// </summary>
  42. public class EuropeanFactory : AbstractFactory
  43. {
  44. // 制作欧式房顶
  45. public override Roof CreateRoof()
  46. {
  47. return new EuropeanRoof();
  48. }
  49.  
  50. // 制作欧式地板
  51. public override Floor CreateFloor()
  52. {
  53. return new EuropeanFloor();
  54. }
  55.  
  56. // 制作欧式窗户
  57. public override Window CreateWindow()
  58. {
  59. return new EuropeanWindow();
  60. }
  61.  
  62. // 制作欧式房门
  63. public override Door CreateDoor()
  64. {
  65. return new EuropeanDoor();
  66. }
  67. }
  68.  
  69. /// <summary>
  70. /// 现在风格房子的工厂,负责创建现代风格的房子
  71. /// </summary>
  72. public class ModernizationFactory : AbstractFactory
  73. {
  74. // 制作现代房顶
  75. public override Roof CreateRoof()
  76. {
  77. return new ModernizationRoof();
  78. }
  79.  
  80. // 制作现代地板
  81. public override Floor CreateFloor()
  82. {
  83. return new ModernizationFloor();
  84. }
  85.  
  86. // 制作现代窗户
  87. public override Window CreateWindow()
  88. {
  89. return new ModernizationWindow();
  90. }
  91.  
  92. // 制作现代房门
  93. public override Door CreateDoor()
  94. {
  95. return new ModernizationDoor();
  96. }
  97. }
  98.  
  99. /// <summary>
  100. /// 房顶抽象类,子类的房顶必须继承该类
  101. /// </summary>
  102. public abstract class Roof
  103. {
  104. /// <summary>
  105. /// 创建房顶
  106. /// </summary>
  107. public abstract void Create();
  108. }
  109.  
  110. /// <summary>
  111. /// 地板抽象类,子类的地板必须继承该类
  112. /// </summary>
  113. public abstract class Floor
  114. {
  115. /// <summary>
  116. /// 创建地板
  117. /// </summary>
  118. public abstract void Create();
  119. }
  120.  
  121. /// <summary>
  122. /// 窗户抽象类,子类的窗户必须继承该类
  123. /// </summary>
  124. public abstract class Window
  125. {
  126. /// <summary>
  127. /// 创建窗户
  128. /// </summary>
  129. public abstract void Create();
  130. }
  131.  
  132. /// <summary>
  133. /// 房门抽象类,子类的房门必须继承该类
  134. /// </summary>
  135. public abstract class Door
  136. {
  137. /// <summary>
  138. /// 创建房门
  139. /// </summary>
  140. public abstract void Create();
  141. }
  142.  
  143. /// <summary>
  144. /// 欧式地板类
  145. /// </summary>
  146. public class EuropeanFloor : Floor
  147. {
  148. public override void Create()
  149. {
  150. Console.WriteLine("创建欧式的地板");
  151. }
  152. }
  153.  
  154. /// <summary>
  155. /// 欧式的房顶
  156. /// </summary>
  157. public class EuropeanRoof : Roof
  158. {
  159. public override void Create()
  160. {
  161. Console.WriteLine("创建欧式的房顶");
  162. }
  163. }
  164.  
  165. /// <summary>
  166. ///欧式的窗户
  167. /// </summary>
  168. public class EuropeanWindow : Window
  169. {
  170. public override void Create()
  171. {
  172. Console.WriteLine("创建欧式的窗户");
  173. }
  174. }
  175.  
  176. /// <summary>
  177. /// 欧式的房门
  178. /// </summary>
  179. public class EuropeanDoor : Door
  180. {
  181. public override void Create()
  182. {
  183. Console.WriteLine("创建欧式的房门");
  184. }
  185. }
  186.  
  187. /// <summary>
  188. /// 现代的房顶
  189. /// </summary>
  190. public class ModernizationRoof : Roof
  191. {
  192. public override void Create()
  193. {
  194. Console.WriteLine("创建现代的房顶");
  195. }
  196. }
  197.  
  198. /// <summary>
  199. /// 现代的地板
  200. /// </summary>
  201. public class ModernizationFloor : Floor
  202. {
  203. public override void Create()
  204. {
  205. Console.WriteLine("创建现代的地板");
  206. }
  207. }
  208.  
  209. /// <summary>
  210. /// 现代的窗户
  211. /// </summary>
  212. public class ModernizationWindow : Window
  213. {
  214. public override void Create()
  215. {
  216. Console.WriteLine("创建现代的窗户");
  217. }
  218. }
  219.  
  220. /// <summary>
  221. /// 现代的房门
  222. /// </summary>
  223. public class ModernizationDoor : Door
  224. {
  225. public override void Create()
  226. {
  227. Console.WriteLine("创建现代的房门");
  228. }
  229. }

2.6、 抽象工厂应对需求变更

让我们看看该模式如何应对需求的变化,假设我的表弟一看我们的房子很好,他也想要一套古典风格的房子(哈哈,这个家伙事挺多的,有好事总是落不下他)。

  1. /// <summary>
  2. ///先为表弟的房子来建立一个工厂类吧
  3. /// </summary>
  4. public class ClassicalFactory : AbstractFactory
  5. {
  6. //创建房顶
  7. public override Roof CreateRoof()
  8. {
  9. return new ClassicalRoof();
  10. }
  11.  
  12. // 创建地板
  13. public override Floor CreateFloor()
  14. {
  15. return new ClassicalFloor();
  16. }
  17.  
  18. // 创建窗户
  19. public override Window CreateWindow()
  20. {
  21. return new ClassicalWindow();
  22. }
  23.  
  24. // 创建房门
  25. public override Door CreateDoor()
  26. {
  27. return new ClassicalDoor();
  28. }
  29. }
  30.  
  31. /// <summary>
  32. ///古典的房顶
  33. /// </summary>
  34. public class ClassicalRoof : Roof
  35. {
  36. public override void Create()
  37. {
  38. Console.WriteLine("创建古典的房顶");
  39. }
  40. }
  41.  
  42. /// <summary>
  43. /// 古典的地板
  44. /// </summary>
  45. public class ClassicalFloor : Floor
  46. {
  47. public override void Create()
  48. {
  49. Console.WriteLine("创建古典的地板");
  50. }
  51. }
  52.  
  53. /// <summary>
  54. /// 古典的窗户
  55. /// </summary>
  56. public class ClassicalWindow : Window
  57. {
  58. public override void Create()
  59. {
  60. Console.WriteLine("创建古典的窗户");
  61. }
  62. }
  63.  
  64. /// <summary>
  65. /// 古典的房门
  66. /// </summary>
  67. public class ClassicalDoor: Door
  68. {
  69. public override void Create()
  70. {
  71. Console.WriteLine("创建古典的房门");
  72. }
  73. }

此时,只需要添加五个类:一个是古典风格工厂类,负责创建古典风格的房子,另外几个类是具有古典风格的房顶、地板、窗户和房门的具体产品。从上面代码看出,抽象工厂对于系列产品的变化支持 “开放——封闭”原则(指的是要求系统对扩展开放,对修改封闭),扩展起来非常简便,但是,抽象工厂对于增加新产品这种情况就不支持”开放——封闭 “原则,因为要修改创建系列产品的抽象基类AbstractFactory,增加相应产品的创建方法,这也是抽象工厂的缺点所在。

三、抽象工厂的实现要点

     1、如果没有应对“多系列对象创建”的需求变化,则没有必要使用AbstractFactory模式,这时候使用简单的静态工厂完全可以。

2、"系列对象"指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中“道路”与“房屋”的依赖,“道路”与“地道”的依赖。

3、AbstractFactory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。

4、AbstractFactory模式经常喝FactoryMethod模式共同组合来应对“对象创建”的需求变化。

3.1】、抽象工厂模式的优点:【抽象工厂】模式将系列产品的创建工作延迟到具体工厂的子类中,我们声明工厂类变量的时候是使用的抽象类型,同理,我们使用产品类型也是抽象类型,这样做就尽可能的可以减少客户端代码与具体产品类之间的依赖,从而降低了系统的耦合度。耦合度降低了,对于后期的维护和扩展就更有利,这也就是【抽象工厂】模式的优点所在。可能有人会说在Main方法里面(这里的代码就是客户端的使用方)还是会使用具体的工厂类,对的。这个其实我们通过Net的配置,把这部分移出去,最后把依赖关系放到配置文件中。如果有新的需求我们只需要修改配置文件,根本就不需要修改代码了,让客户代码更稳定。依赖关系肯定会存在,我们要做的就是降低依赖,想完全去除很难,也不现实。

3.2】、抽象工厂模式的缺点:有优点肯定就有缺点,因为每种模式都有他的使用范围,或者说要解决的问题,不能解决的问题就是缺点了,其实也不能叫缺点了。【抽象工厂】模式很难支持增加新产品的变化,这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

3.3】、抽象工厂模式的使用场景:   如果系统需要多套的代码解决方案,并且每套的代码方案中又有很多相互关联的产品类型,并且在系统中我们可以相互替换的使用一套产品的时候可以使用该模式,客户端不需要依赖具体实现。

四、.NET中抽象工厂模式实现

微软的类库发展了这么多年,设计模式在里面有大量的应用,【抽象工厂】模式在.NET类库中也存在着大量的使用,比如和操作数据库有关的类型,这个类就是System.Data.Common.DbProviderFactory,这个类位于System.Data.dll程序集中。该类扮演抽象工厂模式中抽象工厂的角色,我们可以用ILSpy反编译工具查看该类的实现:

/// 扮演抽象工厂的角色
/// 创建连接数据库时所需要的对象集合,
/// 这个对象集合包括有 DbConnection对象(这个是抽象产品类,如绝味例子中的YaBo类)、DbCommand类、DbDataAdapter类,针对不同的具体工厂都需要实现该抽象类中方法,

  1. public abstract class DbProviderFactory
  2. {
  3. public virtual bool CanCreateDataSourceEnumerator
  4. {
  5. get
  6. {
  7. return false;
  8. }
  9. }
  10.  
  11. public virtual DbCommand CreateCommand()
  12. {
  13. return null;
  14. }
  15.  
  16. public virtual DbCommandBuilder CreateCommandBuilder()
  17. {
  18. return null;
  19. }
  20.  
  21. public virtual DbConnection CreateConnection()
  22. {
  23. return null;
  24. }
  25.  
  26. public virtual DbConnectionStringBuilder CreateConnectionStringBuilder()
  27. {
  28. return null;
  29. }
  30.  
  31. public virtual DbDataAdapter CreateDataAdapter()
  32. {
  33. return null;
  34. }
  35.  
  36. public virtual DbParameter CreateParameter()
  37. {
  38. return null;
  39. }
  40.  
  41. public virtual CodeAccessPermission CreatePermission(PermissionState state)
  42. {
  43. return null;
  44. }
  45.  
  46. public virtual DbDataSourceEnumerator CreateDataSourceEnumerator()
  47. {
  48. return null;
  49. }
  50. }
  51. }

DbProviderFactory类是一个抽象工厂类,该类提供了创建数据库连接时所需要的对象集合的接口,实际创建的工作在其子类工厂中进行,微软使用的是SQL Server数据库,因此提供了连接SQL Server数据的具体工厂实现,具体代码可以用反编译工具查看,具体代码如下:

SqlClientFactory扮演着具体工厂的角色,用来创建连接SQL Server数据所需要的对象

  1. public sealed class SqlClientFactory : DbProviderFactory, IServiceProvider
  2. {
  3. public static readonly SqlClientFactory Instance = new SqlClientFactory();
  4.  
  5. public override bool CanCreateDataSourceEnumerator
  6. {
  7. get
  8. {
  9. return true;
  10. }
  11. }
  12.  
  13. private SqlClientFactory()
  14. {
  15. }
  16.  
  17. public override DbCommand CreateCommand()
  18. {
  19. return new SqlCommand();
  20. }
  21.  
  22. public override DbCommandBuilder CreateCommandBuilder()
  23. {
  24. return new SqlCommandBuilder();
  25. }
  26.  
  27. public override DbConnection CreateConnection()
  28. {
  29. return new SqlConnection();
  30. }
  31.  
  32. public override DbConnectionStringBuilder CreateConnectionStringBuilder()
  33. {
  34. return new SqlConnectionStringBuilder();
  35. }
  36.  
  37. public override DbDataAdapter CreateDataAdapter()
  38. {
  39. return new SqlDataAdapter();
  40. }
  41.  
  42. public override DbParameter CreateParameter()
  43. {
  44. return new SqlParameter();
  45. }
  46.  
  47. public override CodeAccessPermission CreatePermission(PermissionState state)
  48. {
  49. return new SqlClientPermission(state);
  50. }
  51.  
  52. public override DbDataSourceEnumerator CreateDataSourceEnumerator()
  53. {
  54. return SqlDataSourceEnumerator.Instance;
  55. }
  56.  
  57. object IServiceProvider.GetService(Type serviceType)
  58. {
  59. object result = null;
  60. if (serviceType == GreenMethods.SystemDataCommonDbProviderServices_Type)
  61. {
  62. result = GreenMethods.SystemDataSqlClientSqlProviderServices_Instance();
  63. }
  64. return result;
  65. }
  66. }

OdbcFactory也是具体工厂类

  1. public sealed class OdbcFactory : DbProviderFactory
  2. {
  3. public static readonly OdbcFactory Instance = new OdbcFactory();
  4.  
  5. private OdbcFactory()
  6. {
  7. }
  8.  
  9. public override DbCommand CreateCommand()
  10. {
  11. return new OdbcCommand();
  12. }
  13.  
  14. public override DbCommandBuilder CreateCommandBuilder()
  15. {
  16. return new OdbcCommandBuilder();
  17. }
  18.  
  19. public override DbConnection CreateConnection()
  20. {
  21. return new OdbcConnection();
  22. }
  23.  
  24. public override DbConnectionStringBuilder CreateConnectionStringBuilder()
  25. {
  26. return new OdbcConnectionStringBuilder();
  27. }
  28.  
  29. public override DbDataAdapter CreateDataAdapter()
  30. {
  31. return new OdbcDataAdapter();
  32. }
  33.  
  34. public override DbParameter CreateParameter()
  35. {
  36. return new OdbcParameter();
  37. }
  38.  
  39. public override CodeAccessPermission CreatePermission(PermissionState state)
  40. {
  41. return new OdbcPermission(state);
  42. }
  43. }

当然,我们也有OleDbFactory 类型,都是负责具体的数据库操作。DbProviderFactory就是【抽象工厂】模式UML里面AbstractFactory类型。其他具体的工厂类型继承DbProviderFactory类型,这个结构很简单,我就不画图了。

五、总结

终于写完了,写了3个小时,学习设计模式不能死学,要把握核心点和使用场景。关键点第一是,面向对象设计模式的基本原则,有了原则,考虑问题就不会跑偏,然后再仔细把握每种模式的使用场景和要解决的问题,多写写代码,多看看Net的类库,它是最好的教材。

C#设计模式之四抽象工厂模式(AbstractFactory)【创建型】的更多相关文章

  1. 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern)

    原文:乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factory Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 抽象工厂模式(Abstract Factor ...

  2. Java 设计模式之抽象工厂模式(三)

    原文地址:Java 设计模式之抽象工厂模式(三) 博客地址:http://www.extlight.com 一.前言 上篇文章 <Java 设计模式之工厂模式(二)>,介绍了简单工厂模式和 ...

  3. java设计模式之抽象工厂模式学习

    工厂模式有个问题就是,类的创建依赖工厂.要想增加一个工厂类,就要修改原来的代码,这违背了闭包原则.所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的 ...

  4. java设计模式(三)--抽象工厂模式

    转载:http://zz563143188.iteye.com/blog/1847029 前面的工厂方法模式虽然清晰,但还是感觉有些繁琐,通常使用的还是抽象工厂模式. 工厂方法模式有一个问题就是,类的 ...

  5. 03.设计模式_抽象工厂模式(Abstract Fcatory)

    抽象工厂模式:创建一些列相关或者互相依赖的对象的接口,而无需指定他们具体的类, 1.创建工厂Factory: package patterns.design.factory; import java. ...

  6. [java] java 设计模式(2):抽象工厂模式(Abstract Factory)

    工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这 ...

  7. C#设计模式之三抽象工厂模式(AbstractFactory)【创建型】

    一.引言 写了3篇有关设计模式的文章了,大家有了些反馈,说能从中学到一些东西,我感到很欣慰,那就继续努力.今天我要写第四个模式了,该模式叫抽象工厂.上一篇文章我们讲了[工厂方法]模式,它是为了解决[简 ...

  8. 再起航,我的学习笔记之JavaScript设计模式06(抽象工厂模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...

  9. C#设计模式(4)-抽象工厂模式

    引言 上一篇介绍了设计模式中的简单工厂模式-C#设计模式(3)-工厂方法模式,本篇将介绍抽象工厂模式: 抽象工厂模式简介 抽象工厂模式(AbstractFactory):提供一个创建一系列相关或相互依 ...

随机推荐

  1. 用IO流中的File类来创建文件及目录

    题目如下: 设计一个命令窗口程序,要求按照如下的格式显示 例如:===利用命令窗口完成文件的操作===(1)创建目录 (2)创建文件 (3)删除目录 (4)删除文件 ===现在正在做创建目录的操作== ...

  2. .net到Java那些事儿--structs做了那些事(二)

    一.跟着项目先来看下structs怎么执行的      首先看下web.xml配置文件,下面有如下代码 <filter> <filter-name>struts2</fi ...

  3. oracle之备份详解

    1.冷备份(执行冷备份前必须关闭数据库) 物理备份(备份物理数据库文件) 2.热备份(热备份是当数据库正在运行时进行数据备份的过程.执行热备份的前提是:数据库运行在可归档日志模式.适用于24X7不间断 ...

  4. 转每天一个linux命令(4):mkdir命令

    linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录. 1.命令格式: mkdir [选项] 目录... 2.命令 ...

  5. hdu4746 Mophues

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4746 题意:给出n, m, p,求有多少对a, b满足gcd(a, b)的素因子个数<=p,(其 ...

  6. linux下用OCI库访问oracle数据库返回错误Cannot create OCI environment!;

    linux下链接oracle数据库,直接用OCI库函数OCI_Initialize初始化返回Oracle ErrorString:Cannot create OCI environment! 原因是缺 ...

  7. JSP的getRequestDispatcher()与sendRedirect()的区别

    getRequestDispatcher()与sendRedirect()的区别   1.request.getRequestDispatcher()是请求转发,前后页面共享一个request ; r ...

  8. spring-session 共享

    Spring session 共享 一.引入依赖 <dependency> <groupId>redis.clients</groupId> <artifac ...

  9. APPIUM 输入中文 之套路

    java  appium 在Capabilities 中添加 capabilities.setCapabilities("unicodeKeyboard",true);和 capa ...

  10. 有向图和拓扑排序Java实现

    package practice; import java.util.ArrayDeque; import java.util.Iterator; import java.util.Stack; pu ...