领域驱动设计:对象工厂

news/2024/10/19 11:36:36/

一、工厂概述

1.工厂与构造函数

许多面向对象语言都支持类通过构造函数创建它自己。

对象自己创建自己,就好像自己扯着自己的头发离开地球表面,完全不合情理,只是开发人员已经习以为常了。然而,构造函数差劲的表达能力与脆弱的封装能力,在面对复杂的构造逻辑时,显得有些力不从心。

遵循“最小知识法则”,我们不能让调用者了解太多创建的逻辑,以免加重其负担,并带来创建代码的四处泛滥,何况创建逻辑在未来很有可能发生变化。基于以上因素考虑,有必要对创建逻辑进行封装。领域驱动设计引入工厂(factory)承担这一职责。

2.工厂与设计模式

工厂是创建产品对象的一种隐喻。《设计模式:可复用面向对象软件的基础》的创建型模式引入了工厂方法(factory method)模式抽象工厂(abstract factory)模式构建者(builder)模式,可在封装创建逻辑、保证创建逻辑可扩展的基础上实现产品对象的创建。

除此之外,通过定义静态工厂方法创建产品对象的简单工厂模式也因其简单性得到了广泛使用。

领域驱动设计的工厂并不限于使用哪一种设计模式。一个类或者方法只要封装了聚合对象的创建逻辑,都可以被认为是工厂。

2.工厂的职责

将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域设计的一部分。

工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象。

除了创建对象之外,工厂并不需要承担领域模型中的其他职责。

对于聚合来说,我们应该一次性地创建整个聚合,并且确保它的不变条件得到满足。

二、工厂的优点

在DDD中,工厂是生产领域模型的地方,特别是聚合。它为用户抽象了对象的创建过程,通过制定专门的语义(通用语言)和更精细地控制对象的实例化过程来达到这一目的。简而言之,工厂模式的主要目的是生产对象的实例并提供给调用者。

工厂的优点如下:

1.解耦:分离领域职责与创建工序

工厂的主要目的是分离模型的领域职责及其复杂的创建工序。模型创建本身就是一个复杂的操作,尤其在面对大而丰富的领域和关系众多的聚合时更是如此。工厂和聚合是天生的好搭档,因为聚合不仅要初始化各类数据,还要体现某种装配规则。把这个任务交给用户,显然超出了他们的意愿与能力范围。

模型本身并不适合承担装配自己的复杂操作,如果将其领域职责与创建逻辑混在一起,则会破坏领域模型的纯洁性。

同时,创建模型的职责也不适合放到应用层中。虽然应用层领域模型的用户将使用领域模型来实现用例和完成需求,但装配方式在某种程度上仍是一种领域逻辑,应用层代表了“业务”,但绝不代表“业务逻辑”。

所以,创建复杂对象是领域层的职责,但同时又不适合放在领域模型内部。因此,我们需要一个单独的领域对象来负责创建模型,不承担其他领域逻辑,这个对象就是工厂。

第一种实现方式:应用层创建对象。

java">public class CartService {public void add(Product product , Guid cartId){cart = cartRepository.of(cartId);rate = TaxRateService.obtainTaxRateFor(product,country.id);item = new CartItem(rate,product,id,product.price);cart.add(item);}
}

以上是一个跨境电商的购物车对象,添加一个购物项,必须计算相应税率。我们可以看到,应用层必须理解这个逻辑才能够创建购物项,这样领域逻辑就泄露到了应用层。下面是改进后的设计。

第二种实现方式:非工厂领域模型创建对象。

java">public class CartService {public void add(Product product){cart = cartRepository.of(cartId);cart.add(product);}
}
//领域层
public class Cart {public void add(Product product){rate = TaxRateService.obtainTaxRateFor(product,country.id);item = new CartItem(rate,product,id,product.price);items.add(item);}
}

这一版使用领域对象Cart来创建购物项,将领域逻辑保留在领域层。然而,购物车对象Cart和税费计算服务TaxRateService之间不可避免地产生了耦合,这破坏了Cart的纯洁性,未来任何与购物车内在逻辑无关的变化都会影响到购物车。这是自找麻烦,因此我们用工厂模式将两者解耦是最佳选择。

第三种实现方式:工厂创建对象。

java">//领域层
public class Cart {public void add(Product product){rate = TaxRateService.obtainTaxRateFor(product,country.id);item = new CartItem(rate,product,id,product.price);items.add(CartItemFactory.createCartItemFor(product));}
}
//工厂
public class CartItemFactory {public static CartItem createCartItemFor(Product product,Country country){rate = TaxRateService.obtainTaxRateFor(product,country.id);item = new CartItem(rate,product,id,product.price);return item;}
}

工厂隐藏了创建对象的细节,从而购物车无须再关心与其内在业务逻辑无关的创建信息。

2.通用语言:让创建过程体现业务含义

工厂可以让模型更好地表达通用语言。为什么会这样呢?我们换个思路就明白了,工厂创建实例时,命名方法不一定是GetInstanceOf×××而可以根据通用语言来命名,如BookTicket(预订车票)、ScheduleMeeting(安排会议)、RegisterUser(注册用户)、Offer-Invitation(发送邀请)、ScheduleCalendarEntry(添加日程)。这些操作本质上都是要创建新对象的工厂方法,并且表现力要强得多。

java">public class Customer{public Ticket bookTicket(TicketInfo ticketInfo){Ticket aTicket = new Ticket(this.id,ticketInfo);DomainPublisher.instance.publish(new TicketBookedEvent);return aTicket;}
}

BookTicket是客户对象中的一个工厂方法,用于返回一个车票的实例。我们把创建车票的工厂方法放在了Customer对象中,这与前面的不应将创建方法放入对象的说法并不矛盾,因为Customer是聚合根,在聚合根中放置创建成员的工厂是合适的。并非所有的工厂都是单独的领域服务,只要没产生多余的耦合,就可以灵活选择厂址。

事实上,很多工厂方法都不叫工厂,它们将创建新对象的操作与领域的通用语言相结合,使DDD的模型和代码都更具表现力。

3.验证:确保所创建的聚合处于正确状态

在工厂中创建新对象时,可以添加逻辑验证以确保创建出的聚合符合领域内在规则。

例如,在聚合根账户上创建订单,要满足账户必须有足够的余额。

java">public class Account {public Order createOrder() {//如果余额充足if(hasEnoughMoney){return new Order(this.id,this.address);}else {//否则抛出异常throw new MoneyNotEnoughException();}}
}

工厂是领域层的对象,在其中包含领域逻辑是合理且必要的。

4.多态:为一种接口生产多个组件

什么是多态?简单来说,多态即“多种实现形态”之意。这些实现形态是针对抽象方法的多种实现形态,它们位于接口和抽象类之中。从业务上来讲,就是一个操作的多种实现方式。一个支持抽象对象的工厂能够根据业务需要灵活地返回所需的具体类,同时使工厂的用户与具体的类完全解耦。这是符合OCP的完美解决方案。

例如,一个订单需要创建一个快递对象,完成该订单的派送。但使用哪一种快递类型(比如不同的快递公司)是由货物和目的地决定的,显然,这个业务逻辑的变化频率很高,它不适合放在订单对象内,交由工厂来创建对应的快递对象比较合适。

java">public class DeliveryFactory {public static Delivery getDeliveryFor(){if(isRemote()){return new EMSDelivery();}if(isCommonProduct()){return new YuanTongDelivery();}else {return new ShunfengDelivery();}}}

其中,EMSDelivery、YuanTongDelivery、ShunfengDelivery都是具体的执行类,按照一定的业务逻辑由DeliveryFactory返回。它们都是Delivery抽象类的子类,因此可以完成订单中定义的Delivery对象的任务,而订单无须关心是谁来完成,创建订单类的程序员也可以不考虑这个复杂的问题。
在这里插入图片描述
把选择逻辑分离在工厂内,减轻了模型的负担,并使领域模型符合开闭原则,通过针对接口(抽象类)编程,订单不再关心具体类,因此后续我们修改快递选择逻辑或扩展快递类型时,都无须修改订单模型,极大提升了模型的稳定性。

5.重建:重建已存储的对象

虽然DDD不关注技术复杂性,但领域模型实例会被持久化存储在数据库中或序列化成文件以供网络传输。工厂的最后一个目的就是重建已存储的对象,将散布在文件或数据库中的各个部分重新组装成一个可用的对象。

不能要求模型的用户在创建模型时除了调用代码还要做其他的工作,比如访问数据库或文件,也不应让他们生成资源文件。因此,基于持久化机制的对象重建一定要封装在工厂之内。

重建工厂与创建新对象的工厂有以下两个不同点:

  • 创建实体对象时,新对象是生成新的标识符,而重建工厂则是获取已有的标识符ID。
  • 模型或聚合的内在领域逻辑不满足时,新对象工厂可以直接拒绝生成对象,而重建工厂生成的对象违背规则时,需要设计师采用一种纠错机制,比如默认值等策略来处理冲突。

三、工厂的实现方式

工厂的实现主要表现为以下形式:

  • 由被依赖聚合担任工厂;
  • 引入专门的聚合工厂;
  • 聚合自身担任工厂;
  • 消息契约模型或装配器担任工厂;
  • 使用构建者组装聚合。

1.由被依赖聚合担任工厂

领域驱动设计虽然建议引入工厂创建聚合,但并不要求必须引入专门的工厂类,而是可由一个聚合担任另一个“聚合的工厂”。担任工厂角色的聚合称为“聚合工厂”,被创建的聚合称为“聚合产品”。聚合工厂往往由被引用的聚合来承担,如此就可以将自己拥有的信息传给被创建的聚合产品。例如,Blog聚合可以作为Post聚合的工厂:

java">//博客的聚合
public class Blog {//工厂方法是实例方法,无须传入idpublic Post createPost(String title, String content){//这里的id是Blog的id,通过value()传入Post,建立与Blog的关联return new Post(this.id.value(),title,content,this.authorId);}
}

PostService领域服务作为调用者,可通过Blog聚合创建文章:

java">public class PostService {public void writePost(String title,String content){private BlogRepository blogRepository;private PostRepository postRepository;Blog blog = blogRepository.blogOf(BlogId.of(blogId));Post post = blog.createPost(title,content);}
}

由于创建方法会产生聚合工厂与聚合产品之间的依赖,若二者位于不同限界上下文,遵循菱形对称架构的要求,应当避免这一设计。

2.引入专门的聚合工厂

当创建的聚合属于一个多态的继承体系时,构造函数就无能为力了。例如,航班Flight聚合本身形成了一个继承体系,并组成图所示的聚合:
在这里插入图片描述
根据进出港标志,可确定该航班针对当前机场究竟为进港航班还是离港航班,从而创建不同的子类。由于子类的构造函数无法封装这一创建逻辑,我们又不能将创建逻辑的判断职责“转嫁”给调用者,就有必要引入专门的FlightFactory工厂类:

java">public class FlightFactory {public static Flight createFlight(String flightId,String ioFlag,String airportCode){if(ioFlag.equals("A")){return new ArrivalFlight(flightId,airportCode);}else {return new DepartualFlight(flightId,airportCode);}}
}

由于不建议聚合依赖于访问外部资源的端口,引入专门工厂类的另一个好处是可以通过它依赖端口获得创建聚合时必需的值。

例如,在创建跨境电商平台的商品聚合时,海外商品的价格采用了不同的汇率,在创建商品时,需要将不同的汇率按照当前的汇率牌价统一换算为人民币。汇率换算器ExchangeRateConverter需要调用第三方的汇率换算服务,实际上属于商品上下文南向网关的客户端端口。工厂类ProductFactory会调用它:

java">public class ProductFactory {@Autowiredprivate ExchangeRateConverter converter;public Product createProduct(String name, String description , Price price){Money valueOfPrice = converter.convert(price.getValue());return new Product(name,description,new Price(valueOfPrice));}
}

由于需要通过依赖注入将适配器实现注入工厂类,故而该工厂类定义的工厂方法为实例方法。为了防止调用者绕开工厂直接实例化聚合,可考虑将聚合根实体的构造函数声明为包范围内限制,并将聚合工厂与聚合产品放在同一个包。

3.聚合自身担任工厂

聚合产品自身也可以承担工厂角色。这是一种典型的简单工厂模式,例如由Order类定义静态方法,封装创建自身实例的逻辑:

java">public class Order {private Order(CustomerId customerId,ShippingAddress shippingAddress,Basket basket){...}public static Order createOrder(CustomerId customerId,ShippingAddress shippingAddress,Basket basket){return new Order(customerId,shippingAddress,basket);}
}

这一设计方式无须多余的工厂类,创建聚合对象的逻辑也更加严格。由于静态工厂方法属于产品自身,因此可将聚合产品的构造函数定义为私有。调用者除了通过公开的工厂方法获得聚合对象,别无他法可寻。

当聚合作为自身实例的工厂时,该工厂方法不必死板地定义为create××× ()。可以使用诸如of()、instanceOf()等方法名,使得调用代码看起来更加自然:

java">Order order = Order.of(customerId,shippingAddress,basket);

不只聚合的工厂,对于领域模型中的实体与值对象(包括ID类),都可以考虑定义这样具有业务含义或提供自然接口的静态工厂方法,使得创建逻辑变得更加合理而贴切。

4.消息契约模型或装配器担任工厂

设计服务契约时,如果远程服务或应用服务接收到的消息是用于创建的命令请求,则消息契约与领域模型之间的转换操作,实则是聚合的工厂方法。

例如,买家向目标系统发起提交订单的请求就是创建Order聚合的命令请求。该命令请求包含了创建订单需要的客户ID、配送地址、联系信息、购物清单等信息,这些信息被封装到PlacingOrderRequest消息契约模型对象中。

响应买家请求的是OrderController远程服务,它会将该消息传递给应用服务,再进入领域层发起对聚合的创建。应用服务在调用领域服务时,需要将消息契约模型转换为领域模型,也就是调用消息契约模型的转换方法toOrder()。它实际上就是创建Order聚合的工厂方法:

java">public class PlacingOrderRequest {//创建Order聚合的工厂方法public Order toOrder(){}
}
public class OrderAppService{private OrderService orderService;@Transactionalpublic void placeOrder(PlacingOrderRequest  orderRequest){orderService.placeOrder(orderRequest.toOrder());}
}

如果消息契约模型持有的信息不足以创建对应的聚合对象,可以在北向网关层定义专门的装配器,将其作为聚合的工厂。它可以调用南向网关的端口获取创建聚合需要的信息。

5.使用构建者组装聚合

聚合作为相对复杂的自治单元,在不同的业务场景可能需要有不同的创建组合。一旦需要多个参数进行组合创建,构造函数或工厂方法的处理方式就会变得很笨拙,需要定义各种接收不同参数的方法响应各种组合方式。构造函数尤为笨拙,毕竟它的方法名是固定的。如果构造参数的类型与个数一样,含义却不相同,构造函数更是无能为力。

构建者模式有两种实现风格。一种风格是单独定义Builder类,由它对外提供组合构建聚合对象的API。单独定义的Builder类可以与产品类完全分开,也可以定义为产品类的内部类。例如对航班聚合对象的创建:

java">public class Flight{private String flightNo;private Carrier carrier;private Gate boardingGate;private LocalDate flightDate;public Builder prepareBuilder(String flightNo){return new Builder(flightNo);}public class Builder {private String flightNo;private Carrier carrier;private Builder(String flightNo){this.flightNo = flightNo;}public Builder beCarriedBy(String airlineCode){Carrier carrier = new Carrier(airlineCode);return this;}public Builder boardingOn(String airlineCode){boardingGate= new Gate(airlineCode);return this;}public Builder flightDate(LocalDate flyingInDate){flightDate = flyingInDate;return this;}public Flight build(){return new Flight(this);}}private Flight(Builder builder){flightNo = builder.flightNo;carrier= builder.carrier;boardingGate = builder.boardingGate;flightDate  = builder.flightDate;}
}

客户端可以使用如下的流畅接口创建Flight聚合:

java">
Flight flight = Flight.prepareBuilder("CA4116").beCarriedBy("CA").boardingOn("C29").flyingIn(LocalData.of(2019,8,8)).build();

构建者的构建方法可以对参数施加约束条件,避免非法值传入。在上述代码中,由于实体属性大多数被定义为值对象,故而构建方法对参数的约束被转移到了值对象的构造函数中。定义构建方法时,要结合自然语言风格与领域逻辑为方法命名,使得调用代码看起来更像进行一次英文交流。

另一种实现风格是由被构建的聚合对象担任近乎Builder的角色,然后将可选的构造参数定义到每个单独的构建方法中,并返回聚合对象自身以形成流畅接口。仍然以Flight聚合根实体为例:

java">public class Flight{private String flightNo;private Carrier carrier;//聚合必备的参数需要在构造函数中给出private Flight(String flightNo){this.flightNo = flightNo;}public static Flight withFlightNo(String flightNo){return new Flight(flightNo);}public Flight beCarriedBy(String airlineCode){this.carrier = new Carrier(airlineCode);return this;}public Builder boardingOn(String airlineCode){this.boardingGate= new Gate(airlineCode);return this;}public Builder flightDate(LocalDate flyingInDate){this.flightDate = flyingInDate;return this;}
}

相较于第一种风格,它的构建方式更为流畅。从调用者角度看,它没有显式的构建者类,也没有强制要求在构建最后调用build()方法:

java">Flight flight = Flight.withFlightNo("CA4116").beCarriedBy("CA").boardingOn("C29").flyingIn(LocalData.of(2019,8,8));

四、厂址的选择

1.聚合根担任工厂

应用场景:对象属于聚合根内部,与聚合根”同生共死“

如果往一个聚合内添加元素,可以在聚合根上添加一个工厂方法,这样聚合内部的元素的生成细节,外部就无须关心了。同时,因为聚合的内在原则检查都在聚合根内,所以可以保证添加的元素都符合领域内在规则。

例如,订单的聚合根上创建订单项、支付对象等

java">public class Order implements AggregateRoot {private List<OrderItem> orderItems;private Pay pay;public Pay createPay(String orderId){return new Pay(orderId);}}

基于聚合成员与聚合根的“同生共消亡”的特性,将工厂建在聚合根上是合理的,可以检查生成的成员对象是否符合内在逻辑。

这时候注意聚合根内部成员的类应该在同一包中,且构造函数应该为protected,保证只能让聚合根才能创建该对象。

2.”信息专家“工厂

信息专家模式是把职责分配给具有完成该职责所需信息的那个模型。我们在分配模型职责的时采用的是信息专家模式,这个模式也可以用在厂址的选择上。

比如,如果一个对象A拥有创建另一个对象B所需的信息,则可以在A上构建一个工厂方法用于创建B。这种就近原则可以避免把A的信息提取到其他地方,增加不必要的复杂性,最重要的是往往B和A有深层的领域连接逻辑,可以通过工厂得以体现。与上面聚合根上工厂的区别在于,A和B不一定属于一个聚合。

应用场景:聚合根之间有自然的业务流转关系
例如,我们把购物车添加进来。虽然购物车不属于订单聚合,但按照信息专家原则,它拥有创建订单所需的所有信息,依然可以负责订单的创建。

java">public class Cart implements AggregateRoot {private List<CartItem> cartItems;public Order createOrder(List<CartItem> cartItems){return new order(cartItems);}
}

在购物车对象上定义订单的工厂方法是再自然不过的了,这样做并没有增加购物车的负担,也没有增加耦合度。否则,我们需要把购物车的信息提取到额外的对象中,这会模糊购物车到订单这种自然的业务流转关系。

3.领域服务类工厂

将工厂单独地构建为领域服务是一种不错的方法,也是最常用的工厂形式。

前两种厂址的选择方式必须在不增加模型负担和耦合度,且创建新对象的流程比较自然的情况下才可以进行。如果这种衔接关系并不明显,且会影响模型的职责,增加耦合度,那么还是要秉持初衷,分离模型的领域职责及其创建环节,构建单独的工厂对象来创建复杂对象和聚合,实现形式一般是领域服务。整个聚合的创建由一个单独的工厂完成,工厂负责把对根的引用传递出去,并确保创建出的聚合满足领域内在规则。

应用场景:连接不同的限界上下文
应用场景之一是把一个不同上下文的模型翻译成另一个上下文的模型:

java">public class CustomerCreatorService {private List<CartItem> cartItems;public Customer CustomerFrom(String loginId){return cusomerRepository.findBy(loginId);}
}

应用场景:创建对象需要引用外部接口

参考前面代码,若创建对象需要引用外部资源接口,例如需要rpc请求等,则建议用专门的领域服务类工厂进行封装。

应用场景:为一种接口生产多个组件

参考前面代码,把多态的选择逻辑分离在工厂内,减轻了模型的负担,并使领域模型符合开闭原则,通过针对接口(抽象类)编程,调用者不再关心具体类,因此后续我们修改快递选择逻辑或扩展快递类型时,都无须修改领域模型,极大提升了模型的稳定性。

4.只需使用构造函数的场合

是否任何模型的创建都要经过工厂呢?恰恰相反,我们应该优先使用构造函数而不是工厂,因为领域模型并不一定都是复杂对象或聚合。如果在不需要解耦、不需要创建聚合、不需要表达通用语言、没有内在规则或不需要多态的场合,应该直接使用构造函数new,因为构造函数更简单、方便。另外,没有参与工厂建模的团队成员可能意识不到工厂的存在,而直接使用构造函数,这也是模型构建团队需要注意的地方。

具体来说,若满足以下条件,则可直接选择简单的、公共构造函数。

  • 对象是值对象,且不是任何相关层次结构的一部分,而且不需要创建对象多态性。
  • 客户关心的是具体类,而不是只关心接口。
  • 客户可以访问对象的所有属性,且模型没有嵌套对象的创建。
  • 构造环节并不复杂,客户端创建代价不高。
  • 构造函数必须满足工厂的相同规则:创建过程必须是一个原子操作,且能满足领域内在规则。

5.厂名选择的注意事项

  • 1)选择与领域含义相关的命名,如BookTicket(预订车票)、ScheduleMeeting(安排会议)。
  • 2)将Create与要创建的类型名连在一起,以此来命名工厂方法,如CreateWhiteBoard。
  • 3)将创建的类型名与Factory连接在一起,以此来命名工厂类型。例如,可以将创建Role对象的工厂类型命名为RoleFactory。

http://www.ppmy.cn/news/1443352.html

相关文章

Python轻量级Web框架Flask(12)—— Flask类视图实现前后端分离

0、前言&#xff1a; 在学习类视图之前要了解前后端分离的概念&#xff0c;相对于之前的模板&#xff0c;前后端分离的模板会去除views文件&#xff0c;添加两个新python文件apis和urls&#xff0c;其中apis是用于传输数据和解析数据 的&#xff0c;urls是用于写模板路径的。 …

基于Flask的岗位就业可视化系统(一)

前言 本项目综合了基本数据分析的流程&#xff0c;包括数据采集&#xff08;爬虫&#xff09;、数据清洗、数据存储、数据前后端可视化等 推荐阅读顺序为&#xff1a;数据采集——>数据清洗——>数据库存储——>基于Flask的前后端交互&#xff0c;有问题的话可以留言…

计算机网络面试高频:输入域名会发生那些操作,开放性回答

更多大厂面试内容可见 -> http://11come.cn 计算机网络面试高频&#xff1a;输入域名会发生那些操作&#xff0c;开放性回答 输入域名之后&#xff0c;会发生哪些操作&#xff1f; 当在浏览器中输入www.baidu.com并按下回车键时&#xff0c;会触发一系列复杂的网络过程&am…

Unity 物体触碰事件监听

声明委托 public delegate void MyDelegate(Collider trigger); C# 委托&#xff08;Delegate&#xff09; | 菜鸟教程 (runoob.com)https://www.runoob.com/csharp/csharp-delegate.html 定义委托 public MyDelegate onTriggerEnter; public MyDelegate onTriggerStay; pu…

Flask框架进阶-Flask流式输出和受访配置--纯净详解版

Flask流式输出&#x1f680; 在工作的项目当中遇到了一种情况&#xff0c;当前端页面需要对某个展示信息进行批量更新&#xff0c;如果直接将全部的数据算完之后&#xff0c;再返回更新&#xff0c;则会导致&#xff0c;前端点击刷新之后等待时间过长&#xff0c;开始考虑到用进…

怎么检查ubuntu22.04服务器机器被挖矿了

如果怀疑你的 Ubuntu 22.04 系统被挖矿程序占用&#xff0c;可以通过一系列检查步骤来确认这一疑问。这些步骤可以帮助你发现系统是否存在未授权的挖矿活动&#xff1a; 1. 检查CPU和GPU使用率 挖矿程序通常会导致CPU或GPU的使用率异常升高。使用以下命令检查系统资源的使用情…

RAGFlow:安装与体验

服务器需要有docker,或者直接访问官方提供的demo: https://demo.ragflow.io/ docker-compose安装 需要确保 vm.max_map_count 不小于 262144 【更多】:sysctl -w vm.max_map_count=262144 克隆仓库:$ git clone https://github.com/infiniflow/ragflow.git 进入 doc…

【智能优化算法】蛇优化算法(Snake Optimizer,SO)

蛇优化算法(Aquila Optimizer,SO)是期刊“Knowledge-Based Systems”&#xff08;中科院一区&#xff1a;IF 8.8 &#xff09;的2022年智能优化算法 01.引言 蛇优化算法(Aquila Optimizer,SO)以解决模仿蛇特殊交配行为的各种优化任务。如果存在的食物量足够且温度低&#xff0…