zoukankan      html  css  js  c++  java
  • .NET框架设计(高级框架架构模式)—钝化程序、逻辑冻结、冻结程序的延续、瞬间转移

    阅读目录:

    • 1.开篇介绍
    • 2.程序书签(代码书签机制)
      • 2.1ProgramBookmark 实现(使用委托来锚点代码书签)
      • 2.2ProgramBookmarkManager书签管理器(对象化书签集合的处理,IEnumerable<T>书签管理) 
    • 3.可恢复语句组件(将语句对象化)
      • 3.1可恢复语句组件管理器(将可恢复语句视为普通的对象成员,IEnumerable<T>可恢复语句组件)
      • 3.2可恢复语句组件运行时(Program CLR(简介))
      • 3.3可恢复语句逻辑配置(规则的配置(简介))
      • 3.4可恢复语句逻辑传输(将逻辑语句对象远程传输(简介))  
    • 4.DomainModel规则引擎(规则持久化后管理配置(简介))

    1】开篇介绍

    这一篇文章我早准备写的,迟迟未写的原因是它过于抽象不太容易表达,也很难掌握;之前对它的理解还处于比较简单的功能性上,但是最近随着对领域驱动设计及架构的研究,设计思想有了一个提升对它的理解也有了一个更清晰的轮廓,所以才敢下手去写,这么好的一篇文章不能搞砸了;

    “钝化语句” 简单描述:将基于栈的调用抽象成基于我们自己构建的虚拟运行时调用;

    比如我们可以将普通的IFELSE调用进行对象化,然后就可以对他们进行面向对象的设计了;能做的事情就太多了,比如将所有的方法放入一个for循环语句组件当中去,它会自动的去循环执行,而不需要我们再去自己写for语句;然后在此基础上进行代码书签抽象对所有的代码片段进行类似逻辑锚点的设定;

    更吓人的是可以瞬间将语句组件钝化,其实也就是瞬间冻结然后持久化,在遥远的地方再将它唤醒执行,很可能你的语句在你这台电脑上执行了一半由于你临时有事然后语句被钝化,在另外一台电脑上继续你的工作,是不是很方便;当然它的使用方式多种多样了;

    我相信这篇文章绝对让你对 .NET框架设计 感兴趣,框架设计思想其实真的很美,让人陶醉;

    2】程序书签(代码书签机制)

    美好的一切都要有一个良性的开始,程序的钝化少不了对程序的逻辑保存的功能;有一个连续的调用穿过N个方法,方法一调用方法二,方法二调用方法三,这样的调用层次是根据业务的需求来定的,就好比一个复杂的业务逻辑这样的处理下去合情合理;

    那么什么是代码书签呢?其实我们仔细分析一下我们日常所写的代码基本上都是由方法组合而成,不管是实例类还是静态类都是通过方法将彼此联系起来,所有的业务逻辑都是包装在方法的内部处理的,这里的代码书签就是方法的可持久化抽象;

    试想一下,我们要想将程序的逻辑流程钝化肯定是少不了对逻辑调用的保存;原本的程序逻辑是线程本地的执行路径,属于.NETCLR直接管理的,依赖于栈的执行,所以我们无法干预其生命周期过程,那么我们只有将它们对象化后才能由我们自己操控;

    图1:

    上图的意思是说在一个流程的开始到结束基本上三个重要环节,BeginProcesss…End过程,在每个过程中需要不同的处理逻辑,在图的偏上方,我们有三个ProcessName名称的小方块表示程序的调用顺序,ProcessName1调用ProcessName2调用ProcessName3;

    在ProcessName2的上面我们加了一个Bookmark的标记,表示我们这里所说的代码书签,通过代码书签我们就可以记录下本次执行到哪里了,就好比我们在看书的时候都有一个买书时赠送的书签卡,我们看到哪里就把这个书签卡插在那里,当下次要看的时候直接找到这个书签卡继续看;

    这里的代码书签跟这个是一样的道理,理论就是这些我们下面通过示例代码来亲身体验一下这种设计模式;

    2.1】ProgramBookmark 实现(使用委托来锚定代码书签)

    委托是天生的方法标签,通过委托我们完全可以将一个实例的方法直接锚定下来;

    【有关对委托的高级应用不太清楚的可以参见本人的这两篇文章:

    .NET框架设计(一:常被忽视的C#设计技巧).NET框架设计(二:常被忽视的框架设计技巧)

    我们来构造代码书签对象:

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8 namespace ProgramComponent
     9 {
    10     using System;
    11   
    12     /// <summary>
    13     /// Program book mark.
    14     /// </summary>
    15     [Serializable]
    16     public class ProgramBookmark
    17     {
    18         /// <summary>
    19         /// Mark program book mark.
    20         /// </summary>
    21         /// <param name="name">Mark name.</param>
    22         /// <param name="continueAt">Program continue.</param>
    23         public ProgramBookmark(string name, ProgramBookmarkLocation continueAt)
    24         {
    25             this.markname = name;
    26             this.continueAt = continueAt;
    27         }
    28   
    29         private string markname;
    30         /// <summary>
    31         /// Book mark name.
    32         /// </summary>
    33         public string BookmarkName { get { return markname; } }
    34   
    35         private ProgramBookmarkLocation continueAt;
    36         /// <summary>
    37         /// Continue location.
    38         /// </summary>
    39         public ProgramBookmarkLocation ContinueAt { get { return continueAt; } }
    40   
    41         /// <summary>
    42         /// Program load data.
    43         /// </summary>
    44         public object Payload { get; set; }
    45     }
    46     /// <summary>
    47     /// Program book mark location.
    48     /// </summary>
    49     /// <param name="resumed">Resumed bookmark.</param>
    50     public delegate void ProgramBookmarkLocation(ProgramBookmark resumed);
    51 }
    View Code

    这段代码是对代码书签的抽象,构造函数传入一个代码书签的名称、书签所表示的物理代码锚点,Payload是表示每次执行物理代码时的输入参数;

    上面代码看似简单其实很不简单,它的背后隐藏着一个很大的设计思想:

    将一块很大的逻辑代码拆成很多零碎的方法片段,很多人可能会觉得设计本身不就这样要求的嘛,那你可能真的没有深入理解代码碎片会后需要对所有的方法参数进行对象化,不管什么方法都会是同样的参数,只有这样才能让书签连续起作用;

    下面我们来看一下代码书签有多巧妙,我们来构造一个简单的示例代码,当然你完全可以设计的很复杂很强大,这里毕竟是传递这种设计思想为主;

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8 using System;
     9 using System.Collections.Generic;
    10 using System.Linq;
    11 using System.Text;
    12 using System.Threading.Tasks;
    13   
    14 namespace ConsoleApplication1
    15 {
    16     [Serializable]
    17     public class OrderCheckFlows
    18     {
    19         private IList<ProgramComponent.ProgramBookmark> flowsManager = new List<ProgramComponent.ProgramBookmark>();
    20   
    21         public OrderCheckFlows()
    22         {
    23             ProgramComponent.ProgramBookmark bookmarkCheckOrderPrices =
    24                 new ProgramComponent.ProgramBookmark("checkPrices", new ProgramComponent.ProgramBookmarkLocation(CheckOrderPrices));
    25   
    26             flowsManager.Add(bookmarkCheckOrderPrices);
    27         }
    28   
    29         public void StartCheck()
    30         {
    31             do
    32             {
    33                 flowsManager[0].ContinueAt(flowsManager[0]);
    34             }
    35             while (flowsManager.Count > 0);
    36         }
    37   
    38         #region business flows
    39         public void CheckOrderPrices(ProgramComponent.ProgramBookmark nextCheck)
    40         {
    41             Console.WriteLine("checkPrices...");
    42   
    43             ProgramComponent.ProgramBookmark bookmarkCheckOrderPrices =
    44                 new ProgramComponent.ProgramBookmark("checkPrices", new ProgramComponent.ProgramBookmarkLocation(CheckOrderItems));
    45             bookmarkCheckOrderPrices.Payload = true;//method parameters.
    46             flowsManager.Add(bookmarkCheckOrderPrices);
    47   
    48             flowsManager.RemoveAt(0);
    49         }
    50   
    51         public void CheckOrderItems(ProgramComponent.ProgramBookmark nextCheck)
    52         {
    53             if ((bool)nextCheck.Payload)
    54             {
    55                 Console.WriteLine("checkItems...");
    56             }
    57             else
    58             {
    59                 Console.WriteLine("end check items.");
    60             }
    61             flowsManager.RemoveAt(0);
    62         }
    63         #endregion
    64     }
    65 }
    View Code

    这个类是一个简单的模拟检查订单的一系列的业务流程;

    图2:

    上图能看见流程顺利执行完毕了,那么我们来解释一下重要的代码片段;

    图3:

    在第一个流程里面我们构造一个通往下一个流程的 ProgramComponent.ProgramBookmark 对象,如果这里出现关于流程无法继续下去的条件就可以不创建往下执行的代码书签;在第二流程里面我们获取第一个流程设置的参数,这里是一个Bool值,可以用来判断上一个执行是否成功等信息;

    2.2】ProgramBookmarkManager书签管理器(书签集合的处理,IEnumerable<T>书签管理)

    上一节我们完成了对代码书签的抽象实现,但是代码还有很多值得抽象设计的地方,上面的代码中最不太理解的地方就是对书签集合的操作上,很不OO;

    那么这一节我们将把它改进,形成OO方式的调用,先看一下哪里不太理解;

    图4:

    第一个地方就是在声明ProgramCompoent.ProgramBookmark集合上,这样写问题太大了,无法进行扩展改进;然后就是在构造函数中,我们使用了很长一段代码来构造一个ProgramCompoent.ProgramBookmark对象,完全可以减少很多;还有就是在StartCheck方法的内部中进行循环调用书签的代码,也很有问题,完全可以封装在内部实现,外部直接一个CurrentProgram属性执行就行了;

    那么对这些问题我们其实少一个ProgramCompoent.ProgramBookmark的管理器对象ProgramCompoent.ProgramBookmarkManager对象,它负责管理所有跟ProgramCompoent.ProgramBookmark对象相关的工作;

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8   
     9 namespace ProgramComponent
    10 {
    11     using System.Collections.Generic;
    12   
    13     /// <summary>
    14     /// Program book mark Manager.<see cref="System.Collections.Dictionary{BookmarkName,ProgramBookmark}"/>
    15     /// </summary>
    16     public class ProgramBookmarkManager : Dictionary<string, ProgramBookmark>
    17     {
    18         /// <summary>
    19         /// Add programbookmark and instant next programbookmark.
    20         /// </summary>
    21         /// <param name="bookmark"><see cref="ProgramComponent.ProgramBookmark"/></param>
    22         public void Add(ProgramBookmark bookmark)
    23         {
    24             base.Add(bookmark.BookmarkName, bookmark);
    25         }
    26         /// <summary>
    27         /// Remove programbookmark.
    28         /// </summary>
    29         /// <param name="bookmark"><see cref="ProgramComponent.ProgramBookmark"/></param>
    30         public void Remove(ProgramBookmark bookmark)
    31         {
    32             base.Remove(bookmark.BookmarkName);
    33         }
    34         /// <summary>
    35         /// Resume  bookmark by bookmarkname.
    36         /// </summary>
    37         /// <param name="bookmarkName">bookmark name.</param>
    38         /// <param name="payload">Continue load.</param>
    39         public void Resume(string bookmarkName, object payload)
    40         {
    41             ProgramBookmark bookmark;
    42             this.TryGetValue(bookmarkName, out bookmark);
    43             if (bookmark != null)
    44             {
    45                 bookmark.Payload = payload;
    46                 bookmark.ContinueAt(bookmark);
    47             }
    48         }
    49     }
    50 }
    View Code

    书签管理器基本功能还算简单,主要的方法Resume是用来恢复指定的书签的;再来看一下订单检查流程调用;

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8 namespace ConsoleApplication1
     9 {
    10     using System;
    11     using ProgramComponent;
    12   
    13     [Serializable]
    14     public class OrderCheckFlows
    15     {
    16         private ProgramBookmarkManager BookmarkManager = new ProgramBookmarkManager();
    17   
    18         public OrderCheckFlows()
    19         {
    20             BookmarkManager.Add(new ProgramBookmark("checkPrices", new ProgramBookmarkLocation(CheckOrderPrices)));
    21         }
    22   
    23         public void StartCheck()
    24         {
    25             BookmarkManager.Resume("checkPrices", null);
    26         }
    27   
    28         #region business flows
    29         public void CheckOrderPrices(ProgramComponent.ProgramBookmark nextCheck)
    30         {
    31             Console.WriteLine("checkPrices...");
    32             BookmarkManager.Remove(nextCheck);
    33   
    34             BookmarkManager.Add(new ProgramBookmark("checkItems", new ProgramBookmarkLocation(CheckOrderItems)));
    35             BookmarkManager.Resume("checkItems", true);
    36         }
    37   
    38         public void CheckOrderItems(ProgramComponent.ProgramBookmark nextCheck)
    39         {
    40             if ((bool)nextCheck.Payload)
    41                 Console.WriteLine("checkItems...");
    42             else
    43                 Console.WriteLine("end check items.");
    44   
    45             BookmarkManager.Remove(nextCheck);
    46         }
    47         #endregion
    48     }
    49 }
    View Code

    是不是比之前的代码好多了,我感觉是好多了,当然还有很大的重构空间;

    这里其实已经可以和链式编程的机制挂钩了,我们可以通过给书签管理器添加N个扩展方法来使书签管理器具有跟链式的调用;

    3】可恢复语句组件(将可恢复语句对象化)

    要想把所有的调用都拆开来使用松散的方式组合,通过使用书签机制基本上能将所有的方法进行松散组合;那么我们还需要将逻辑语法进行对象化才能做到无死角的松散;

    什么叫语句组件,就是将一些原本无法独立的一些逻辑判断、循环之类的语句对象化,形成更具有对象的组件;

    试想一下,如果我们将所有的这些逻辑语法对象化后我们的代码中还有精密耦合的代码吗?就算有也应该会很少,是不是很神奇;

    其实对 企业应用架构 中的 规约模式 有所了解的人应该会比较熟悉这一节的内容,跟规约模式很像,但不是一个东西,侧重点不同;语句组件全面的概念是将所有的调用都对象化,包括一些输出、输入、网络调用等等,这样才是全部的语句组件定义,还记得我们上面的订单检查对象嘛,那个也是语句组件之一;

    我们来构造可恢复语句组件对象;

    ProgramComponent.LanguageComponent.LanguageComponent类代码:

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8 namespace ProgramComponent.LanguageComponent
     9 {
    10     using System;
    11   
    12     [Serializable]
    13     public abstract class LanguageComponent
    14     {
    15         public abstract void Run(ProgramBookmarkManager mgr);
    16     }
    17 }
    View Code

    ProgramComponent.LanguageComponent.LanguageComponentBlock类代码:

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8   
     9 namespace ProgramComponent.LanguageComponent
    10 {
    11     using System.Collections.Generic;
    12   
    13     public class LanguageComponentBlock : LanguageComponent
    14     {
    15         List<LanguageComponent> statements = new List<LanguageComponent>();
    16         public List<LanguageComponent> Statements
    17         {
    18             get { return statements; }
    19         }
    20   
    21         public void AddLangugateComponent(LanguageComponent lc)
    22         {
    23             statements.Add(lc);
    24         }
    25   
    26         public override void Run(ProgramBookmarkManager mgr)
    27         {
    28               
    29         }
    30     }
    31 }
    View Code

    ProgramComponent.LanguageComponent.IfElseLanguageComponent类代码:

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8 namespace ProgramComponent.LanguageComponent
     9 {
    10     using System;
    11     using System.Linq;
    12     using System.Linq.Expressions;
    13   
    14     public class IfElseLanguageComponent : LanguageComponentBlock
    15     {
    16         public Func<bool> Exp { get; set; }
    17   
    18         public override void Run(ProgramBookmarkManager mgr)
    19         {
    20             if (Exp())
    21                 base.Statements[0].Run(mgr);
    22             else
    23                 base.Statements[1].Run(mgr);
    24         }
    25     }
    26 }
    View Code

    检查流程代码,OrderCheckFlowsOrderSubmitFlows类代码:

     1 /*==============================================================================
     2  * Author:深度训练
     3  * Create time: 2013-08-10
     4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
     5  * Author Description:特定领域软件工程实践;
     6  *==============================================================================*/
     7   
     8 namespace ConsoleApplication1
     9 {
    10     using System;
    11     using ProgramComponent;
    12     using ProgramComponent.LanguageComponent;
    13   
    14     [Serializable]
    15     public class OrderCheckFlows : LanguageComponent
    16     {
    17         private ProgramBookmarkManager BookmarkManager = new ProgramBookmarkManager();
    18   
    19         public OrderCheckFlows(ProgramBookmarkManager bookmarkManager)
    20         {
    21             this.BookmarkManager = bookmarkManager;
    22             BookmarkManager.Add(new ProgramBookmark("checkPrices", new ProgramBookmarkLocation(CheckOrderPrices)));
    23         }
    24         public override void Run(ProgramBookmarkManager mgr)
    25         {
    26             this.BookmarkManager = mgr;
    27             StartCheck();
    28         }
    29         public void StartCheck()
    30         {
    31             BookmarkManager.Resume("checkPrices", null);
    32         }
    33   
    34         #region business flows
    35         public void CheckOrderPrices(ProgramComponent.ProgramBookmark nextCheck)
    36         {
    37             Console.WriteLine("checkPrices...");
    38             BookmarkManager.Remove(nextCheck);
    39   
    40             BookmarkManager.Add(new ProgramBookmark("checkItems", new ProgramBookmarkLocation(CheckOrderItems)));
    41             BookmarkManager.Resume("checkItems", true);
    42         }
    43   
    44         public void CheckOrderItems(ProgramComponent.ProgramBookmark nextCheck)
    45         {
    46             if ((bool)nextCheck.Payload)
    47                 Console.WriteLine("checkItems...");
    48             else
    49                 Console.WriteLine("end check items.");
    50   
    51             BookmarkManager.Remove(nextCheck);
    52         }
    53         #endregion
    54     }
    55   
    56     [Serializable]
    57     public class OrderSubmitFlows : LanguageComponent
    58     {
    59         private ProgramBookmarkManager BookmarkManager = new ProgramBookmarkManager();
    60   
    61         public OrderSubmitFlows(ProgramBookmarkManager bookmarkManager)
    62         {
    63             this.BookmarkManager = bookmarkManager;
    64             BookmarkManager.Add(new ProgramBookmark("CheckSubmitPrices", new ProgramBookmarkLocation(CheckSubmitPrices)));
    65         }
    66   
    67         public override void Run(ProgramBookmarkManager mgr)
    68         {
    69             this.BookmarkManager = mgr;
    70             StartCheck();
    71         }
    72         public void StartCheck()
    73         {
    74             BookmarkManager.Resume("CheckSubmitPrices", null);
    75         }
    76   
    77         #region business flows
    78         public void CheckSubmitPrices(ProgramComponent.ProgramBookmark nextCheck)
    79         {
    80             Console.WriteLine("CheckSubmitPrices...");
    81             BookmarkManager.Remove(nextCheck);
    82   
    83             BookmarkManager.Add(new ProgramBookmark("CheckSubmitItems", new ProgramBookmarkLocation(CheckSubmitItems)));
    84             BookmarkManager.Resume("CheckSubmitItems", true);
    85         }
    86   
    87         public void CheckSubmitItems(ProgramComponent.ProgramBookmark nextCheck)
    88         {
    89             if ((bool)nextCheck.Payload)
    90                 Console.WriteLine("CheckSubmitItems...");
    91             else
    92                 Console.WriteLine("end check CheckSubmitItems.");
    93   
    94             BookmarkManager.Remove(nextCheck);
    95         }
    96         #endregion
    97     }
    98 }
    View Code

    调用代码:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6   
     7 namespace ConsoleApplication1
     8 {
     9     using ProgramComponent;
    10     using ProgramComponent.LanguageComponent;
    11   
    12     class Program
    13     {
    14         static void Main(string[] args)
    15         {
    16             ProgramBookmarkManager bookmarkManager = new ProgramBookmarkManager();
    17   
    18             OrderCheckFlows orderCheckFlow = new OrderCheckFlows(bookmarkManager);
    19             OrderSubmitFlows submitCheckFlow = new OrderSubmitFlows(bookmarkManager);
    20   
    21             IfElseLanguageComponent languageComponent = new IfElseLanguageComponent();
    22             languageComponent.Exp = () => { return true; };
    23             languageComponent.AddLangugateComponent(orderCheckFlow);
    24             languageComponent.AddLangugateComponent(submitCheckFlow);
    25             languageComponent.Run(bookmarkManager);
    26   
    27             Console.ReadLine();
    28         }
    29     }
    30 }
    View Code

    一切都已经被对象化,我们来看一下逻辑;

    图5:

    这里的返回值决定了后面要执行的语句组件的路径,如果是true,则应该检查OrderCheckFlows流程;

    图6:

    如果是false,则应该检查OrderSubmitFlows流程;

    图7:

    可恢复语句对象模型基本构造完成,当然复杂的问题还需要仔细的去分析设计,这里只是一个简单的示例;

    3.1】可恢复语句组件管理器(将可恢复语句视为普通的对象成员,IEnumerable<T>语句组件)

    跟代码书签管理器一个道理,这里我们也可以实现一个LanguageComponentManager来对LanguageComponent管理,当然也要看需要不需要;可恢复语句管理器其实有很多文章可以做,因为它是所有语句组件的中心,这对于后面的持久化有很大的用处;

    //由于内容比较多且相当抽象,下一篇文章介绍;

    3.2】可恢复语句组件运行时(Program CLR)

    所有的语句代码都已经被对象化,但是在运行时需要一个中心来管理这些被对象化的语句组件,因为我们要脱离对栈的依赖;一组语句组件是单个示例流程的一部分,但是我们可能会存在很多一起并行运行的流程,所以这是必须要提供的运行时;

    //由于内容比较多且相当抽象,下一篇文章介绍;

    3.3】可恢复语句逻辑配置(规则的配置)

    领域驱动设计在使用规约模式的时候会存在动态配置的需求,可以参见这里的语句组件模型,让规约最大化的提供配置;

    //由于内容比较多且相当抽象,下一篇文章介绍;

    3.4】可恢复语句逻辑传输(将可恢复语句对象远程传输)

    //由于内容比较多且相当抽象,下一篇文章介绍;

    4】DomainModel规则引擎(规则持久化后管理配置)

    //由于内容比较多且相当抽象,下一篇文章介绍;

    示例DEMO地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication3.zip

  • 相关阅读:
    第六次作业SSM
    第五次作业——MVC2项目实践
    第四次作业——JSP显示新闻
    第三次作业——servlet应用
    第二次作业——模仿登录页面
    第一次作业——Java web基础
    Alpha项目测试
    作业三
    第二次作业
    第一次阅读作业-201731062609-庞斌
  • 原文地址:https://www.cnblogs.com/wangiqngpei557/p/3251637.html
Copyright © 2011-2022 走看看