- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章ABP框架的基础配置及依赖注入讲解由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
配置ABP 配置是通过在自己模块的PreInitialize方法中来实现的 代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
class
SimpleTaskSystemModule : AbpModule
{
public
override
void
PreInitialize()
{
//在你的应用中添加语言包,这个是英语和作者的土耳其语。
Configuration.Localization.Languages.Add(
new
LanguageInfo(
"en"
,
"English"
,
"famfamfam-flag-england"
,
true
));
Configuration.Localization.Languages.Add(
new
LanguageInfo(
"tr"
,
"Türkçe"
,
"famfamfam-flag-tr"
));
Configuration.Localization.Sources.Add(
new
XmlLocalizationSource(
"SimpleTaskSystem"
,
HttpContext.Current.Server.MapPath(
"~/Localization/SimpleTaskSystem"
)
)
);
//配置导航和菜单
Configuration.Navigation.Providers.Add<SimpleTaskSystemNavigationProvider>();
}
public
override
void
Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
|
和orchard类似,abp框架一开始就被设计成模块化的,不同的模块可以通过abp框架来进行配置。举个例子吧,不同的模块都可以添加导航,通过导航添加菜单项到自己定义的主菜单,具体的细节大家可以参照:
本地化:http://www.aspnetboilerplate.com/Pages/Documents/Localization 导航:http://www.aspnetboilerplate.com/Pages/Documents/Navigation 。
配置模块 和.net框架原生的启动配置相比较,abp有哪些不一样呢?abp框架的模块可以通过IAbpModuleConfigurations接口进行个性化的扩展,这样的话,模块配置更加简单、方便。 示例代码如下:
1
2
3
4
5
6
7
8
|
...
using
Abp.Web.Configuration;
...
public
override
void
PreInitialize()
{
Configuration.Modules.AbpWeb().SendAllExceptionsToClients =
true
;
}
...
|
在上面这个例子中,我们通过配置AbpWeb模块,发送异常到客户端。当然了,不是每一个模块都需要这种配置,通常情况下我们需要,是当一个模块需要在多个不同的应用中重复使用,我们才进行这样的配置.
为一个模块创建配置 如下代码,假如我们有一个命名为MyModule的模块,并且这各模块有一些自己的配置。那么我们首先要创建一些类,这些类定义为属性(译者注:属性有自动的get和set访问器。),代表了不同的配置.
1
2
3
4
5
6
|
public
class
MyModuleConfig
{
public
bool
SampleConfig1 {
get
;
set
; }
public
string
SampleConfig2 {
get
;
set
; }
}
|
接下来,我们通过依赖注入,注册这个类.
IocManager.Register<MyModuleConfig>(); //译者注:在IocManager中注册了一个类,换句话说,我们通过IocManager可以得到这个类MyModuleConfig的实例。至于IOC的原理这里就不在详细说了,总之,就是可以得到一个类的实例.
最后,我们通过创建一个扩展的方法IModuleConfigurations来得到配置的引用。如下代码
译者注:模块配置是一个静态类,因为我们需要重复使用它。静态方法Mymodule返回的是一个配置接口,参数是ImoduleConfigurations接口.
现在,在其他模块中也可以配置我们自定义的这个MyModule模块了.
1
2
|
Configuration.Modules.MyModule().SampleConfig1 =
false
;
Configuration.Modules.MyModule().SampleConfig2 =
"test"
;
|
在某种意义上,MyModule需要这些配置,你能注射MyModuleConfig并且可以使用这些值.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
class
MyService : ITransientDependency
{
private
readonly
MyModuleConfig _configuration;
public
MyService(MyModuleConfig configuration)
{
_configuration = configuration;
}
public
void
DoIt()
{
if
(_configuration.SampleConfig2 ==
"test"
)
{
//...
}
}
}
|
这意味着,在abp框架的系统中,所有的模块都可以集中配置.
ABP依赖注入 什么是依赖注入 如果你已经知道依赖注入的概念,构造函数和属性注入模式,你可以跳过这一节.
维基百科说:“依赖注入是一种软件设计模式的一个或多个依赖项注入(或服务),或通过引用传递,为依赖对象(或客户)和客户端状态的一部分。模式之间建立一个客户的依赖关系的行为,它允许程序设计是松散耦合的,依赖倒置和单一职责原则。它直接对比service locator模式,它允许客户了解他们所使用的系统找到依赖。”.
如果不使用依赖注入技术,很难进行依赖管理、模块化开发和应用程序模块化.
传统方式的问题 。
在一个应用程序中,类之间相互依赖。假设我们有一个应用程序服务,使用仓储(repository)类插入实体到数据库。在这种情况下,应用程序服务类依赖于仓储(repository)类。看下例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
PersonAppService
{
private
IPersonRepository _personRepository;
public
PersonAppService()
{
_personRepository =
new
PersonRepository();
}
public
void
CreatePerson(
string
name,
int
age)
{
var person =
new
Person { Name = name, Age = age };
_personRepository.Insert(person);
}
}
|
PersonAppService使用PersonRepository插入Person到数据库。这段代码的问题
PersonAppService通过IPersonRepository调用CreatePerson方法,所以这方法依赖于IPersonRepository,代替了PersonRepository具体类。但PersonAppService(的构造函数)仍然依赖于PersonRepository。组件应该依赖于接口而不是实现。这就是所谓的依赖性倒置原则。 如果PersonAppService创建PersonRepository本身,它成为依赖IPersonRepository接口的具体实现,不能使用另一个实现。因此,此方式的将接口与实现分离变得毫无意义。硬依赖(hard-dependency)使得代码紧密耦合和较低的可重用。 我们可能需要在未来改变创建PersonRepository的方式。即,我们可能想让它创建为单例(单一共享实例而不是为每个使用创建一个对象)。或者我们可能想要创建多个类实现IPersonRepository并根据条件创建对象。在这种情况下,我们需要修改所有依赖于IPersonRepository的类。 有了这样的依赖,很难(或不可能)对PersonAppService进行单元测试。 为了克服这些问题,可以使用工厂模式。因此,创建的仓储类是抽象的。看下面的代码: 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
PersonAppService
{
private
IPersonRepository _personRepository;
public
PersonAppService()
{
_personRepository = PersonRepositoryFactory.Create();
}
public
void
CreatePerson(
string
name,
int
age)
{
var person =
new
Person { Name = name, Age = age };
_personRepository.Insert(person);
}
}
|
PersonRepositoryFactory是一个静态类,创建并返回一个IPersonRepository。这就是所谓的服务定位器模式。以上依赖问题得到解决,因为PersonAppService不需要创建一个IPersonRepository的实现的对象,这个对象取决于PersonRepositoryFactory的Create方法。但是,仍然存在一些问题:
此时,PersonAppService取决于PersonRepositoryFactory。这是更容易接受,但仍有一个硬依赖(hard-dependency)。 为每个库或每个依赖项乏味的写一个工厂类/方法。 测试性依然不好,由于很难使得PersonAppService使用mock实现IPersonRepository。 解决方案:
有一些最佳实践(模式)用于类依赖.
构造函数注入 。
重写上面的例子,如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
PersonAppService
{
private
IPersonRepository _personRepository;
public
PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public
void
CreatePerson(
string
name,
int
age)
{
var person =
new
Person { Name = name, Age = age };
_personRepository.Insert(person);
}
}
|
这被称为构造函数注入。现在,PersonAppService不知道哪些类实现IPersonRepository以及如何创建它。谁需要使用PersonAppService,首先创建一个IPersonRepository PersonAppService并将其传递给构造函数,如下所示:
1
2
3
|
var repository =
new
PersonRepository();
var personService =
new
PersonAppService(repository);
personService.CreatePerson(
"Yunus Emre"
, 19);
|
构造函数注入是一个完美的方法,使一个类独立创建依赖对象。但是,上面的代码有一些问题:
创建一个PersonAppService变得困难。想想如果它有4个依赖,我们必须创建这四个依赖对象,并将它们传递到构造函数PersonAppService。 从属类可能有其他依赖项(在这里,PersonRepository可能有依赖关系)。所以,我们必须创建PersonAppService的所有依赖项,所有依赖项的依赖关系等等. .如此,依赖关系使得我们创建一个对象变得过于复杂了。 幸运的是,依赖注入框架自动化管理依赖关系.
属性注入 。
构造函数注入模式是一个完美的提供类的依赖关系的方式。通过这种方式,您不能创建类的实例,而不提供依赖项。它也是一个强大的方式显式地声明是什么类的需求正确地工作.
但是,在某些情况下,该类依赖于另一个类,但也可以没有它。这通常是适用于横切关注点(如日志记录)。一个类可以没有工作日志,但它可以写日志如果你提供一个日志对象。在这种情况下,您可以定义依赖为公共属性,而不是让他们放在构造函数。想想,如果我们想在PersonAppService写日志。我们可以重写类如下: 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
PersonAppService
{
public
ILogger Logger {
get
;
set
; }
private
IPersonRepository _personRepository;
public
PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
Logger = NullLogger.Instance;
}
public
void
CreatePerson(
string
name,
int
age)
{
Logger.Debug(
"Inserting a new person to database with name = "
+ name);
var person =
new
Person { Name = name, Age = age };
_personRepository.Insert(person);
Logger.Debug(
"Successfully inserted!"
);
}
}
|
NullLogger.Instance 是一个单例对象,实现了ILogger接口,但实际上什么都没做(不写日志。它实现了ILogger实例,且方法体为空)。现在,PersonAppService可以写日志了,如果你为PersonAppService实例设置了Logger,如下面
1
2
3
|
var personService =
new
PersonAppService(
new
PersonRepository());
personService.Logger =
new
Log4NetLogger();
personService.CreatePerson(
"Yunus Emre"
, 19);
|
假设Log4NetLogger实现ILogger实例,使得我们可以使用Log4Net库写日志。因此,PersonAppService可以写日志。如果我们不设置Logger,PersonAppService就不写日志。因此,我们可以说PersonAppService ILogger实例是一个可选的依赖.
几乎所有的依赖注入框架都支持属性注入模式 。
依赖注入框架 。
有许多依赖注入框架,都可以自动解决依赖关系。他们可以创建所有依赖项(递归地依赖和依赖关系)。所以你只需要根据注入模式写类和类构造函数&属性,其他的交给DI框架处理!在良好的应用程序中,类甚至独立于DI框架。整个应用程序只会有几行代码或类,显示的与DI框架交互.
ABP的依赖注入基于 Castle Windsor框架。Castle Windsor最成熟的DI框架之一。还有很多这样的框架,如Unity,Ninject,StructureMap,Autofac等等.
在使用一个依赖注入框架时,首先注册您的接口/类到依赖注入框架中,然后你就可以resolve一个对象。在Castle Windsor,它是这样的:
1
2
3
4
5
6
7
8
9
|
var container =
new
WindsorContainer();
container.Register(
Component.For<IPersonRepository>().ImplementedBy<PersonRepository>().LifestyleTransient(),
Component.For<IPersonAppService>().ImplementedBy<PersonAppService>().LifestyleTransient()
);
var personService = container.Resolve<IPersonAppService>();
personService.CreatePerson(
"Yunus Emre"
, 19);
|
我们首先创建了WindsorContainer。然后注册PersonRepository 和 PersonAppService及它们的接口。然后我们要求容器创建一个IPersonAppService实例。它创建PersonAppService对象及其依赖项并返回。在这个简单的示例中,使用DI框架也许不是那么简洁,但想象下,在实际的企业应用程序中你会有很多类和依赖关系。当然,注册的依赖项只在程序启动的某个地方创建一次.
请注意,我们只是讲对象声明为临时对象(transient)。这意味着每当我们创建这些类型的一个对象时,就会创建一个新的实例。有许多不同的生命周期(如Singletion).
ABP依赖注入的基础结构 在编写应用程序时遵循最佳实践和一些约定,ABP几乎让依赖注入框架使用变得无形.
注册:
在ABP中,有很多种不同的方法来注册你的类到依赖注入系统。大部分时间,常规方法就足够了.
常规注册:
按照约定,ABP自动注册所有 Repositories, Domain Services, Application Services, MVC 控制器和Web API控制器。例如,您可能有一个IPersonAppService 接口和实现类PersonAppService:
1
2
3
4
5
6
7
8
9
|
public
interface
IPersonAppService : IApplicationService
{
//...
}
public
class
PersonAppService : IPersonAppService
{
//...
}
|
ABP会自动注册它,因为它实现IApplicationService接口(它只是一个空的接口)。它会被注册为transient (每次使用都创建实例)。当你注入(使用构造函数注入)IPersonAppService接口成一个类,PersonAppService对象会被自动创建并传递给构造函数.
命名约定在这里非常重要。例如你可以将名字PersonAppService改为 MyPersonAppService或另一个包含“PersonAppService”后缀的名称,由于IPersonAppService包含这个后缀。但是你可以不遵循PeopleService命名您的服务类。如果你这样做,它将不会为IPersonAppService自动注册(它需要自注册(self-registration)到DI框架,而不是接口),所以,如果你想要你应该手动注册它.
ABP按照约定注册程序集。所以,你应该告诉ABP按照约定注册您的程序集。这很容易
。
1
|
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
|
Assembly.GetExecutingAssembly()得到一个对包括此代码的程序集的引用。你可以通过RegisterAssemblyByConvention方法注册其他程序集。这同在你的模块初始化(AbpModule.Initialize())时完成。请查看ABP的模块系统获得更多信息.
您可以通过实现IConventionalRegisterer接口和调用IocManager。AddConventionalRegisterer方法编写自己的约定注册类。你应该将它添加到模块的pre-initialize方法中.
帮助接口 。
你可以注册一个特定的类,不遵循传统的约定制度规则。ABP提供了ITransientDependency和ISingletonDependency接口的快捷方法。例如:
1
2
3
4
5
6
7
8
9
|
public
interface
IPersonManager
{
//...
}
public
class
MyPersonManager : IPersonManager, ISingletonDependency
{
//...
}
|
以这种方式,您可以很容易地注册MyPersonManager为transient。当需要注入IPersonManager时,MyPersonManager会被使用。注意,依赖被声明为单例。因此,创建的MyPersonManager同一个对象被传递给所有需要的类。只是在第一次使用时创建,那么应用程序的整生命周期使用的是同一实例.
自定义/直接 注册 。
如果之前描述的方法还是不足以应对你的情况,你可以使用Castle Windsor注册类和及依赖项。因此,您将拥有Castle Windsor注册的所有能力.
可以实现IWindsorInstaller接口进行注册。您可以在应用程序中创建一个实现IWindsorInstaller接口的类:
1
2
3
4
5
6
7
|
public
class
MyInstaller : IWindsorInstaller
{
public
void
Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<IMySpecialInterface>().LifestylePerThread().WithServiceSelf());
}
}
|
Abp自动发现和执行这个类。最后,你可以通过使用IIocManager.IocContainer属性得到WindsorContainer。有关更多信息,阅读Windsor的文档.
解析(Resolving) 。
注册通知IOC(控制反转)容器关于你的类,它们的依赖项和生命周期。在您的应用程序需要使用IOC容器创建对象时,ASP.NET提供了一些方法解决依赖关系.
构造函数 & 属性注入 。
作为最佳实践,你可以使用构造函数和属性注入去获取你的类的依赖。任何可能的地方,你都应该这样做。例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
PersonAppService
{
public
ILogger Logger {
get
;
set
; }
private
IPersonRepository _personRepository;
public
PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
Logger = NullLogger.Instance;
}
public
void
CreatePerson(
string
name,
int
age)
{
Logger.Debug(
"Inserting a new person to database with name = "
+ name);
var person =
new
Person { Name = name, Age = age };
_personRepository.Insert(person);
Logger.Debug(
"Successfully inserted!"
);
}
}
|
IPersonRepository从构造函数注入,ILogger实例从公共属性注入。这样,您的代码不会体现依赖注入系统。这是使用DI系统最适当的方式.
IIocResolver 和 IIocManager 。
有时你可能需要直接创建你的依赖项,而不是构造函数和属性注入。应该尽可能避免这种情况,但它可能无法避免。Abp提供一些服务使得这样的注入很容易实现。例子:
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
MySampleClass : ITransientDependency
{
private
readonly
IIocResolver _iocResolver;
public
MySampleClass(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
}
public
void
DoIt()
{
//Resolving, using and releasing manually
var personService1 = _iocResolver.Resolve<PersonAppService>();
personService1.CreatePerson(
new
CreatePersonInput { Name =
"Yunus"
, Surname =
"Emre"
});
_iocResolver.Release(personService1);
//Resolving and using in a safe way
using
(var personService2 = _iocResolver.ResolveAsDisposable<PersonAppService>())
{
personService2.Object.CreatePerson(
new
CreatePersonInput { Name =
"Yunus"
, Surname =
"Emre"
});
}
}
}
|
MySampleClass是一个应用程序的示例类。IIcResolver通过构造函数注入,然后用它来创建和释放对象。有几个解决方法的重载可以根据需要使用。Release方法用于释放组件(对象)。如果你是手动创建一个对象,调用Release方法释放对象非常重要。否则,您的应用程序会有内存泄漏问题。为了保证对象被释放,尽可能使用ResolveAsDisposable(就像上面的例子所示)。它会在using代码块结束的时候自动调用Release方法.
如果你想直接使用IOC容器(Castle Windsor)来处理依赖关系项,可以通过构造函数注入 IIocManager并使用它IIocManager.IocContainer 属性。如果你是在一个静态上下文或不能注入IIocManager,还有最后一个方法,你可以使用单例对象IocManager.Instance,你可以在任何地方获取到,它无处不在。但是,在这种情况下你的代码将变得不易容测试.
附加 。
IShouldInitialize 接口:
有些类在第一次使用前需要初始化。IShouldInitialize有Initialize()方法。如果你实现它,那么你的Initialize()方法自动会被自动调用在创建对象之后(在使用之前)。当然,为了使用这个特性,你应该注入/创建此对象.
ASP.NET MVC & ASP.NET Web API 集成:
当然,我们必须调用依赖注入系统处理依赖关系图的根对象。在一个ASP.NET MVC应用程序,通常是一个控制器类。我们可以使用构造函数注入模式注入控制器。当一个请求来到我们的应用程序中,控制器和所有依赖项被IOC容器递归创建。所以,谁做了这些?这是被Abp扩展的ASP.NET MVC默认控制器工厂自动完成的。ASP.NET Web API 也是相似的。你不用关心对象的创建和释放.
最后此篇关于ABP框架的基础配置及依赖注入讲解的文章就讲到这里了,如果你想了解更多关于ABP框架的基础配置及依赖注入讲解的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要开发一个简单的网站,我通常使用 bootstrap CSS 框架,但是我想使用 Gumbyn,它允许我使用 16 列而不是 12 列。 我想知道是否: 我可以轻松地改变绿色吗? 如何使用固定布局
这个问题在这里已经有了答案: 关闭 13 年前。 与直接编写 PHP 代码相比,使用 PHP 框架有哪些优点/缺点?
我开发了一个 Spring/JPA 应用程序:服务、存储库和域层即将完成。 唯一缺少的层是网络层。我正在考虑将 Playframework 2.0 用于 Web 层,但我不确定是否可以在我的 Play
我现有的 struts Web 应用程序具有单点登录功能。然后我将使用 spring 框架创建一个不同的 Web 应用程序。然后想要使用从 struts 应用程序登录的用户来链接新的 spring 应
我首先使用Spark框架和ORMLite处理网页上表单提交的数据,在提交中文字符时看到了unicode问题。我首先想到问题可能是由于ORMLite,因为我的MySQL数据库的字符集已设置为使用utf8
我有一个使用 .Net 4.5 功能的模块,我们的应用程序也适用于 XP 用户。所以我正在考虑将这个 .net 4.5 依赖模块移动到单独的项目中。我怎样才能有一个解决方案,其中有两个项目针对不同的版
我知道这是一个非常笼统的问题,但我想我并不是真的在寻找明确的答案。作为 PHP 框架的新手,我很难理解它。 Javascript 框架,尤其是带有 UI 扩展的框架,似乎通过将 JS 代码与设计分开来
我需要收集一些关于现有 ORM 解决方案的信息。 请随意编写任何编程语言。 你能谈谈你用过的最好的 ORM 框架吗?为什么它比其他的更好? 最佳答案 我使用了 NHibernate 和 Entity
除了 Apple 的 SDK 之外,还有什么强大的 iPhone 框架可供开始开发?有没有可以加快开发时间的方法? 最佳答案 此类框架最大的是Three20 。 Facebook 和许多其他公司都使用
有人可以启发我使用 NodeJS 的 Web 框架吗?我最近开始从免费代码营学习express js,虽然一切进展顺利,但我对express到底是什么感到困惑。是全栈框架吗?纯粹是为了后端吗?我发现您
您可以推荐哪种 Ajax 框架/工具包来构建使用 struts 的 Web 应用程序的 GUI? 最佳答案 我会说你的 AJAX/javascript 库选择应该较少取决于你的后端是如何实现的,而更多
我有生成以下错误的 python 代码: objc[36554]: Class TKApplication is implemented in both /Library/Frameworks/Tk.
首先,很抱歉,如果我问的问题很明显,因为我没有编程背景,那我去吧: 我想运行一系列测试场景并在背景部分声明了几个变量(我打印它们以仔细检查它们是否已正确声明),第一个是整数,另外两个字符串为你可以看到
在我们承担的一个项目中,我们正在寻找一个视频捕获和录制库。我们的基础工作(基于 google 搜索)表明 vlc (libvlc)、ffmpeg (libavcodec) 和 gstreamer 是三
我试过没有运气的情况下寻找某种功能来杀死/中断Play中的正常工作!框架。 我想念什么吗?还是玩了!实际没有添加此功能? 最佳答案 Java stop类中没有像Thread方法那样的东西,由于种种原因
我们希望在我们的系统中保留所有重大事件的记录。例如,在数据库可能存储当前用户状态的地方,事件日志应记录对该状态的所有更改以及更改发生的时间。 事件记录工具应该尽可能接近于事件引发器的零开销,应该容纳结
那里有 ActionScript 2.0/3.0 的测试框架列表吗? 最佳答案 2010-05-18 更新 由于这篇文章有点旧,而且我刚刚收到了赞成票,因此可能值得提供一些更新的信息,这样人们就不会追
我有一个巨大的 numpy 数组列表(一维),它们是不同事件的时间序列。每个点都有一个标签,我想根据其标签对 numpy 数组进行窗口化。我的标签是 0、1 和 2。每个窗口都有一个固定的大小 M。
我是 Play 的新手!并编写了我的第一个应用程序。这个应用程序有一组它依赖的 URL,从 XML 响应中提取数据并返回有效的 URL。 此应用程序需要在不同的环境(Dev、Staging 和 Pro
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 4年前关闭。 Improve thi
我是一名优秀的程序员,十分优秀!