Abstract Factory 抽象工厂模式简介与 C# 示例【创建型1】【设计模式来了_1】
〇、简介
1、什么是抽象工厂模式?
一句话解释:
提供一个接口,以创建一系列相关或相互依赖的抽象对象,而无需指定它们具体的类。(将一系列抽象类装进接口,一次接口实现,就必须实例化这一系列抽象类)
抽象工厂模式(Abstract Factory Pattern)是一种创建型模式。它用于创建一组相关对象的家族。强调的是一组对象之间的协作关系,而不是单个对象之间的依赖关系。抽象工厂类负责创建整个家族的对象的生命周期,并隐藏与实现有关的逻辑。
一个比喻:(科目与课代表)
语文和数学的课代表和副课代表,都按照抽象方法标准选好了,接下来同样的通过实现抽象类和接口标准,来选出两名物理课代表。当然,已经选出来的其他课代表,和本次选举无关联。
2、优缺点和使用场景
优点:
- 可以降低系统中各个对象之间的耦合度。
- 隔离了具体类的生产,使得客户并不需要知道什么被创建。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
- 在增加新的产品方面比较困难,需要修改抽象工厂的接口,这样会导致所有的具体工厂也需要做出相应的修改。
- 抽象程度高,可能会导致一些底层实现细节难以控制。
总之,抽象工厂模式能够有效地封装对象创建,但是扩展产品较为困难。它在软件开发中被广泛使用,特别是在跨平台软件开发中经常用到,使用时要注意系统对象的特点合理使用。
适用场景:
- 一个系统要独立于它的产品的创建、组合和表示。
- 一个系统要有多个产品系列中的一个来配置。
- 要强调一系列相关的产品对象的设计,以便进行联合使用。
- 提供一个产品类库,但只想显示它们的接口而不是实现。
实际使用场景举例:
- 游戏开发:游戏中可能需要多种角色、武器、敌人等元素,它们之间可能存在关联性或依赖性,可以使用抽象工厂方法来快速构建游戏元素。
- 数据库访问组件设计:不同数据库的连接、查询和数据存储方式可能存在差异,可以使用抽象工厂方法来创建不同数据库的访问组件、驱动和映射器。
- 操作系统界面设计:不同操作系统的界面设计具有不同的特点,可以使用抽象工厂方法来创建不同操作系统下的控件。
总之,使用抽象工厂模式,都需要保证对象家族之间高内聚、松耦合,使得系统的设计和实现更加灵活和可扩展。
一、抽象工厂模式简单实现与扩展
通过两个抽象类 ProductA/ProductBBBB,实现四个具体产品类;在通过接口 IAbstractFactory,实现两个具体工厂的产品族 ConcreteFactory1/ConcreteFactory2。最后通过 Client 类注入工厂类的同时,创建产品的不同产品的实例,使客户端不用了解产品如何实例化,可以直接引用。
// 抽象产品类。
public abstract class ProductA
{
public abstract void OperationA();
}
public abstract class ProductBBBB
{
public abstract void OperationBBBB();
}
// 具体产品类,其中 ProductA1、ProductA2、ProductB1 和 ProductB2 分别代表不同的产品。
public class ProductA1 : ProductA
{
public override void OperationA()
{
Console.WriteLine("ProductA1's operation.");
}
}
public class ProductA2 : ProductA
{
public override void OperationA()
{
Console.WriteLine("ProductA2's operation.");
}
}
public class ProductBBBB1 : ProductBBBB
{
public override void OperationBBBB()
{
Console.WriteLine("ProductBBBB1's operation.");
}
}
public class ProductBBBB2 : ProductBBBB
{
public override void OperationBBBB()
{
Console.WriteLine("ProductBBBB2's operation.");
}
}
// 抽象工厂接口,定义了各种不同产品族的生产方法。
public interface IAbstractFactory
{
ProductA CreateProductA();
ProductBBBB CreateProductBBBB();
}
// 每个具体工厂都能够生产特定的产品族。
public class ConcreteFactory1 : IAbstractFactory
{
public ProductA CreateProductA()
{
return new ProductA1();
}
public ProductBBBB CreateProductBBBB()
{
return new ProductBBBB1();
}
}
public class ConcreteFactory2 : IAbstractFactory
{
public ProductA CreateProductA()
{
return new ProductA2();
}
public ProductBBBB CreateProductBBBB()
{
return new ProductBBBB2();
}
}
// 客户端代码使用抽象工厂来创建各种不同产品族的产品,而无需关心它们的实际实现。
public class Client
{
private readonly ProductA _productA;
private readonly ProductBBBB _productBBBB;
public Client(IAbstractFactory factory)
{
_productA = factory.CreateProductA();
_productBBBB = factory.CreateProductBBBB();
}
public void Run()
{
_productA.OperationA();
_productBBBB.OperationBBBB();
}
}
// 测试
static void Main(string[] args)
{
Client client = new Client(new ConcreteFactory1());
client.Run();
Client client2 = new Client(new ConcreteFactory2());
client2.Run();
// 输出:
// ProductA1's operation.
// ProductBBBB1's operation.
// ProductA2's operation.
// ProductBBBB2's operation.
}
下面我们尝试扩展出来一个新的产品 3:
// 具体产品类
public class ProductA3 : ProductA
{
public override void OperationA()
{
Console.WriteLine("ProductA3's operation.");
}
}
public class ProductBBBB3 : ProductBBBB
{
public override void OperationBBBB()
{
Console.WriteLine("ProductBBBB3's operation.");
}
}
// 具体工厂都能够生产特定的产品族
public class ConcreteFactory3 : IAbstractFactory
{
public ProductA CreateProductA()
{
return new ProductA3();
}
public ProductBBBB CreateProductBBBB()
{
return new ProductBBBB3();
}
}
测试:
static void Main(string[] args)
{
Client client = new Client(new ConcreteFactory1());
client.Run();
Client client2 = new Client(new ConcreteFactory2());
client2.Run();
Client client3 = new Client(new ConcreteFactory3());
client3.Run();
}

二、抽象工厂模式的结构
根据上一章节的示例代码,简单画一个 UML 图,如下:

- AbstractFactory:声明一个创建抽象产品对象的操作接口。
- ConcreteFactory:实现创建具体产品对象的操作。
- AbstractProduct:为一类产品对象声明一个接口。
- ConcreteProduct:定义一个将被相应的具体工厂创建的产品对象。实现了 IAbstractProduct 接口。
- Client:仅使用由 AbstractFactory 和 AbstractProduct 类声明的接口。
通常在运行时创建一个 ConcreteFactory 类的实例,此实例具有 AbstractFactory 中全部定义的实现。当客户端需要创建不同的产品对象时,可以通过实现抽象工厂来创建具体工厂。
AbstractFactory 将产品对象的创建延迟到它的 ConcreteFactory 子类。
三、抽象工厂模式在 .net 框架中的实际应用
例如 DbProviderFactory,这个类位于 System.Data.Common.dll 程序集中,该类扮演抽象工厂模式中抽象工厂的角色,源码如下:
// System.Data.Common, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Data.Common.DbProviderFactory
using System.Data.Common;
public abstract class DbProviderFactory
{
private bool? _canCreateDataAdapter;
private bool? _canCreateCommandBuilder;
public virtual bool CanCreateDataSourceEnumerator => false;
public virtual bool CanCreateDataAdapter
{
get
{
if (!_canCreateDataAdapter.HasValue)
{
using DbDataAdapter dbDataAdapter = CreateDataAdapter();
_canCreateDataAdapter = dbDataAdapter != null;
}
return _canCreateDataAdapter.Value;
}
}
public virtual bool CanCreateCommandBuilder
{
get
{
if (!_canCreateCommandBuilder.HasValue)
{
using DbCommandBuilder dbCommandBuilder = CreateCommandBuilder();
_canCreateCommandBuilder = dbCommandBuilder != null;
}
return _canCreateCommandBuilder.Value;
}
}
public virtual DbCommand? CreateCommand()
{
return null;
}
public virtual DbCommandBuilder? CreateCommandBuilder()
{
return null;
}
public virtual DbConnection? CreateConnection()
{
return null;
}
public virtual DbConnectionStringBuilder? CreateConnectionStringBuilder()
{
return null;
}
public virtual DbDataAdapter? CreateDataAdapter()
{
return null;
}
public virtual DbParameter? CreateParameter()
{
return null;
}
public virtual DbDataSourceEnumerator? CreateDataSourceEnumerator()
{
return null;
}
}
下面是 SqlClientFactory.cs,继承了抽象类 DbProviderFactory,需要注意的是,此为引用程序集,即只包含元数据,不含可执行代码。如何通过工厂模式访问 SQLServer 数据库,可以参考官网示例: 获取 DbProviderFactory
点击查看 SqlClientFactory.cs
// System.Data.SqlClient, Version=4.6.1.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// System.Data.SqlClient.SqlClientFactory
using System.Data.Common;
using System.Data.SqlClient;
/// <summary>Represents a set of methods for creating instances of the <see cref="N:System.Data.SqlClient" /> provider's implementation of the data source classes.</summary>
public sealed class SqlClientFactory : DbProviderFactory
{
/// <summary>Gets an instance of the <see cref="T:System.Data.SqlClient.SqlClientFactory" />. This can be used to retrieve strongly typed data objects.</summary>
public static readonly SqlClientFactory Instance;
internal SqlClientFactory()
{
}
/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbCommand" /> instance.</summary>
/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbCommand" />.</returns>
public override DbCommand CreateCommand()
{
throw null;
}
/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbCommandBuilder" /> instance.</summary>
/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbCommandBuilder" />.</returns>
public override DbCommandBuilder CreateCommandBuilder()
{
throw null;
}
/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbConnection" /> instance.</summary>
/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbConnection" />.</returns>
public override DbConnection CreateConnection()
{
throw null;
}
/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbConnectionStringBuilder" /> instance.</summary>
/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbConnectionStringBuilder" />.</returns>
public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{
throw null;
}
/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbDataAdapter" /> instance.</summary>
/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbDataAdapter" />.</returns>
public override DbDataAdapter CreateDataAdapter()
{
throw null;
}
/// <summary>Returns a strongly typed <see cref="T:System.Data.Common.DbParameter" /> instance.</summary>
/// <returns>A new strongly typed instance of <see cref="T:System.Data.Common.DbParameter" />.</returns>
public override DbParameter CreateParameter()
{
throw null;
}
}
下面再看一下 Oracle 工厂的实现,完全独立于其他数据库的工厂:
点击查看 OracleClientFactory.cs
#region 程序集 Oracle.ManagedDataAccess, Version=4.122.21.1, Culture=neutral, PublicKeyToken=89b483f429c47342
// C:\Users\zheng\.nuget\packages\oracle.manageddataaccess\21.10.0\lib\net462\Oracle.ManagedDataAccess.dll
// Decompiled with ICSharpCode.Decompiler 7.1.0.6543
#endregion
using System;
using System.Data.Common;
using System.Security;
using System.Security.Permissions;
using OracleInternal.Common;
namespace Oracle.ManagedDataAccess.Client
{
public sealed class OracleClientFactory : DbProviderFactory
{
public static readonly OracleClientFactory Instance = new OracleClientFactory();
public override bool CanCreateDataSourceEnumerator => true;
public override DbCommand CreateCommand()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommand);
}
try
{
return new OracleCommand();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommand, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommand);
}
}
}
public override DbCommandBuilder CreateCommandBuilder()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommandBuilder);
}
try
{
return new OracleCommandBuilder();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommandBuilder, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateCommandBuilder);
}
}
}
public override DbConnection CreateConnection()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnection);
}
try
{
return new OracleConnection();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnection, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnection);
}
}
}
public override DbConnectionStringBuilder CreateConnectionStringBuilder()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnectionStringBuilder);
}
try
{
return new OracleConnectionStringBuilder();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnectionStringBuilder, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateConnectionStringBuilder);
}
}
}
public override DbDataAdapter CreateDataAdapter()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataAdapter);
}
try
{
return new OracleDataAdapter();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataAdapter, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataAdapter);
}
}
}
public override DbDataSourceEnumerator CreateDataSourceEnumerator()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataSourceEnumerator);
}
try
{
return new OracleDataSourceEnumerator();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataSourceEnumerator, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateDataSourceEnumerator);
}
}
}
public override DbParameter CreateParameter()
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateParameter);
}
try
{
return new OracleParameter();
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateParameter, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreateParameter);
}
}
}
public override CodeAccessPermission CreatePermission(PermissionState state)
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Entry, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreatePermission);
}
try
{
return new OraclePermission(state);
}
catch (Exception ex)
{
OracleException.HandleError(OracleTraceLevel.Public, OracleTraceTag.Error, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreatePermission, ex);
throw;
}
finally
{
if (ProviderConfig.m_bTraceLevelPublic)
{
Trace.Write(OracleTraceLevel.Public, OracleTraceTag.Exit, OracleTraceClassName.OracleClientFactory, OracleTraceFuncName.CreatePermission);
}
}
}
}
}
当然,诸如 Mysql、DB2 等类同。由此可见,当后续新增数据库时,只需对 DbProviderFactory 抽象工厂进行继承即可,对已实现的数据工厂毫无影响。
Abstract Factory 抽象工厂模式简介与 C# 示例【创建型1】【设计模式来了_1】的更多相关文章
- Java设计模式:Abstract Factory(抽象工厂)模式
概念定义 抽象工厂(Abstract Factory)模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 抽象工厂模式中,系统的产品有多于一个的产品族(一个产品族里定义多个产品) ...
- c++ 设计模式9 (Abstract Factory 抽象工厂模式)
5.2 抽象工厂模式 动机:在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作. 代码示例: 实现利用数据库的业务逻辑,支持多数据库(Sq ...
- 设计模式(一): abstract factory抽象工厂模式 -- 创建型模式
1.定义 为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类. 2.适用场景 1.一个系统要独立于它的产品创建.组合和表示. 2.一个系统要由多个产品系列中的一个来配置. 3.当你要 ...
- 一天一个设计模式——Abstract Factory抽象工厂模式
一.模式说明 前面学习了工厂方法(Factory Method)模式.在工厂方法模式中,在工厂方法模式中,父类决定如何生成实例,但并不决定所要生成的具体类,具体的处理交由子类来处理.这里学习的抽象工厂 ...
- Abstract Factory抽象工厂模式
抽象工厂模式是是用一个超级工厂去创建其他工厂,简单点说就是工厂的父类,属于创建型模式. 目标:提供一个创建一组对象的方法,而无需指定它们具体的类(同工厂方法). 使用场景:系统的产品有多于一个的产品族 ...
- 面向对象设计模式纵横谈:Abstract Factory 抽象工厂模式(笔记记录)
今天是设计模式的第二讲,抽象工厂的设计模式,我们还是延续老办法,一步一步的.演变的来讲,先来看看一个对象创建的问题. 1.如何创建一个对象 常规的对象创建方法: 这样的创建对象没有任何问题, ...
- Abstract Factory 抽象工厂模式
提供一个创建一些列相关或相互依赖对象的接口,而无需指定它们具体的类. 抽象工厂顾名思义就是对工厂的抽象,它提供了一组创建抽象产品对象的操作接口,我们实际使用的是抽象工厂的派生类,派生类中提供了操作的具 ...
- C#设计模式之四抽象工厂模式(AbstractFactory)【创建型】
一.引言 写了3篇有关设计模式的文章了,大家有了些反馈,说能从中学到一些东西,我感到很欣慰,那就继续努力.今天我要写第四个模式了,该模式叫抽象工厂.上一篇文章我们讲了[工厂方法]模式,它是为了 ...
- C#设计模式之三抽象工厂模式(AbstractFactory)【创建型】
一.引言 写了3篇有关设计模式的文章了,大家有了些反馈,说能从中学到一些东西,我感到很欣慰,那就继续努力.今天我要写第四个模式了,该模式叫抽象工厂.上一篇文章我们讲了[工厂方法]模式,它是为了解决[简 ...
- 2.设计模式-Abstract Factory 抽象工厂模式
大神勿喷,不对的地方请指出来,学笔记而已. 解决的问题:应对多系列对象构建的变化或多系列(例如:崎岖的山路和平坦的马路属于一个系列) 不断的变化的创建. 使用场景:对象不变(比如有3个对象 " ...
随机推荐
- CSS绘制虚线的方案
一.实现效果 二.代码实现 <div class="line"></div> .line { width: 1px; /* 虚线宽度 */ backgrou ...
- #Powerbi函数学习 SELECTEDVALUE与ISFILTERED
Power BI中的DAX函数ISFILTERED可以用来判断一个表或者一个列是否被筛选器所影响. 这个函数的语法很简单,就是ISFILTERED(<table_or_column_name&g ...
- 2020-12-28:java中,生产环境服务器变慢,如何诊断处理?
福哥答案2020-12-28:答案1:使用 top 指令,服务器中 CPU 和 内存的使用情况,-H 可以按 CPU 使用率降序,-M 内存使用率降序.排除其他进程占用过高的硬件资源,对 Java 服 ...
- 2021-04-07:给定一个非负数组arr,长度为N,那么有N-1种方案可以把arr切成左右两部分,每一种方案都有,min{左部分累加和,右部分累加和},求这么多方案中,min{左部分累加和,右部分累加和}的最大值是多少? 整个过程要求时间复杂度O(N)。
2021-04-07:给定一个非负数组arr,长度为N,那么有N-1种方案可以把arr切成左右两部分,每一种方案都有,min{左部分累加和,右部分累加和},求这么多方案中,min{左部分累加和,右部分 ...
- 2021-12-16:给定两个数a和b, 第1轮,把1选择给a或者b, 第2轮,把2选择给a或者b, ... 第i轮,把i选择给a或者b。 想让a和b的值一样大,请问至少需要多少轮? 来自字节跳动。
2021-12-16:给定两个数a和b, 第1轮,把1选择给a或者b, 第2轮,把2选择给a或者b, - 第i轮,把i选择给a或者b. 想让a和b的值一样大,请问至少需要多少轮? 来自字节跳动. 答案 ...
- pupstudy的使用
打开环境 点击管理--打开根目录 把靶场放在www文件夹里 网页打开127.0.0.1/靶场文件名即可
- flutter 填坑之旅(dart学习笔记篇)
俗话说 '工欲善其事必先利其器' 想要撸flutter app 而不懂 dart 那就像一个不会英语的人在和英国人交流,懵! 安装 dart 就不用说了,比较简单dart 官网 https://dar ...
- 高级程序员和新手小白程序员区别你是那个等级看解决bug速度
IT入门深似海 ,程序员行业,我觉得是最难做的.加不完的班,熬不完的夜. 和产品经理,扯不清,理还乱的宿命关系 一直都在 新需求-做项目-解决问题-解决bug-新需求 好像一直都是这么一个循环.(哈哈 ...
- Java 新的生态,Solon v2.3.2 发布
Solon 是什么框架? 一个,Java 新的生态型应用开发框架.它从零开始构建,有自己的标准规范与开放生态(全球第二级别的生态).与其他框架相比,它解决了两个重要的痛点:启动慢,费资源. 解决痛点? ...
- XXE漏洞详解
XML外部实体注入--XXE漏洞详解 简单来说一下这个XXE漏洞,在这之前我也阅读了很多关于XXE漏洞的文章,发现有一小部分文章题目是 "XXE外部实体注入" 这样的字眼,我想这样 ...