游戏服务器上的Java模式原型
有时也称为克隆,基本上原型是一种生成式设计模式,它允许您复制对象而不使您的代码依赖于它们的类。
主意
这个想法是创造一群怪物来攻击我们的英雄/字符。为敌人设置了一个特定的配置——敌人配置。在原型的帮助下,我们将能够复制基本的敌人并将它们分组。我们还可以复制组并将它们组成大单元。我们可以设置参数,例如 20 个近战敌人和 40 个远程敌人。基于基本配置,我们可以创建单元的副本。这将进一步允许我们克隆整个单位。酷,不是吗?
问题
假设您有一个Enemy 对象,并且您想要创建它的精确副本。你会怎么做?首先,我们需要创建一个相同类的新对象。然后你需要遍历原始对象的所有字段并将它们的值复制到新对象。
美好的!但是有一个问题!并非所有对象都可以以这种方式复制,因为某些字段可能已关闭并且从对象本身外部不可见。
直接方法还有另一个问题。由于我们需要知道对象的类来创建副本,因此我们的代码变得依赖于该类。如果其他成瘾没有吓到我们,还有另一个障碍。有时,您只知道对象遵循的接口,而不知道其特定的类,例如,当方法中的参数接受遵循某个接口的任何对象时。
解决方案
Prototype模板将克隆过程委托给自然克隆的对象。该模板为所有支持克隆的对象声明了一个标准接口。此接口允许您克隆对象,而无需将任何代码绑定到该对象的类。通常,这样的接口只包含一个克隆方法。
所有类中克隆方法的实现都非常相似。该方法创建当前类的对象并将旧对象的所有字段值传输到新对象。您甚至可以复制私有字段,因为大多数编程语言都允许对象访问属于同一类的其他对象的私有字段。
支持克隆的对象称为原型。当您的对象具有数十个字段和数百种可能的配置时,克隆它们可以替代子类化。
它是这样工作的:您创建一组可通过各种方式自定义的对象。当您需要像您自定义的对象一样的对象时,您可以克隆原型,而不是从头开始创建新对象。
适用性
当您的代码不需要依赖于您需要复制的特定对象类时,请使用原型模式。
当您的代码使用通过某个接口从第三方代码传递给您的对象时,通常会发生这种情况。这些对象的具体类别是未知的,即使你想依赖它们也不能依赖它们。
原型模板为客户端代码提供了一个通用接口来处理所有支持克隆的对象。该接口使客户端代码独立于克隆对象的特定类。
原型模式允许您使用一组预先创建的对象,以各种方式配置为原型。
客户端可以找到适当的原型并克隆它,而不是实例化一个子类来匹配某些配置。
如何实施
创建一个原型接口并在其中声明一个克隆方法。或者,如果您有一个方法,则将一种方法添加到现有类层次结构的所有类中。
原型类必须定义一个替代构造函数,该构造函数将该类的对象作为参数。构造函数必须将类中定义的所有字段的值从传递的对象复制到新创建的实例中。如果更改子类,则必须调用父构造函数让超类处理其私有字段的克隆。如果您的编程语言不支持方法重载,您可以定义一个特殊的方法来复制对象数据。构造函数是一个更方便的地方,因为它在调用 new运算符后立即传递结果对象。
克隆方法通常包含一行:使用原型构造函数开始一个新语句。请注意,每个类都必须显式覆盖 clone 方法并将其类名与 new运算符一起使用。否则,clone 方法可以创建父类的对象。
或者,您可以创建一个集中的原型注册表来存储常用原型的目录。使用静态原型获取方法,您可以将注册表实现为新的工厂类或将其放置在原型基类中。此方法应根据客户端代码传递给该方法的搜索条件来搜索原型。这些要求可以是一个简单的字符串标记,也可以是一组复杂的搜索参数。一旦找到匹配的原型,注册中心必须克隆它并将其返回给客户端。最后,将直接调用子类构造函数替换为调用原型注册表工厂方法。
复制敌人
让我们看看如何在没有标准 Cloneable 接口的情况下实现原型。
Step 1: Let’s create a basic abstract class Enemy
示例 1:
Java
// Java Program to Create Abstract Enemy class
// Abstract class
public abstract class Enemy {
// enemy health
public int health;
// enemy speed
public int speed;
// enemy name
public String name;
// Constructor 1
// Base constructor
public Enemy() {
}
// Constructor 2
// Copy constructor
public Enemy(Enemy target) {
// Check that target not empty
if (target != null) {
// set base params and note
// this keyword refers to current instance itself
this.health = target.health;
this.speed = target.speed;
this.name = target.name;
}
}
// clone() method
public abstract Enemy clone();
// Override equals() method
@Override
public boolean equals(Object o) {
if (!(o instanceof Enemy)) return false;
Enemy enemy = (Enemy) o;
return enemy.health == health && enemy.speed == speed && Objects.equals(enemy.name, name);
}
// Override hashCode() method
@Override
public int hashCode() {
return Objects.hash(health, speed, name);
}
}
Java
// Let`s create ArcherEnemy with attack range
public class ArcherEnemy extends Enemy { // Enemy is a parent
public int attackRange; // add new param
public ArcherEnemy() {
}
public ArcherEnemy(ArcherEnemy target) { // clone constructor
super(target); // first of all clone base Enemy
if (target != null) {
this.attackRange = target.attackRange; // then clone additional params
}
}
/*
Clone based on THIS enemy
*/
@Override
public Enemy clone() {
return new ArcherEnemy(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ArcherEnemy that = (ArcherEnemy) o;
return attackRange == that.attackRange ;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), attackRange);
}
}
Java
// Let`s create MeleeEnemy with 'melee params'
public class MeleeEnemy extends Enemy {
public int blockChance; // add new param
public boolean withShield; // add new param
public MeleeEnemy() {}
// Create clone constructor
// clone base Enemy
// and then clone additional params
public MeleeEnemy(MeleeEnemy target)
{
super(target);
if (target != null) {
this.blockChance = target.blockChance;
this.withShield = target.withShield;
}
}
// Clone class based on current values
@Override public Enemy clone()
{
return new MeleeEnemy(this);
}
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
if (!super.equals(o))
return false;
MeleeEnemy that = (MeleeEnemy)o;
return blockChance == that.blockChance
&& withShield == that.withShield;
}
@Override public int hashCode()
{
return Objects.hash(super.hashCode(), blockChance,
withShield);
}
}
Java
// Java Program to Illustrate Creation of Clones
// Main class
public class Demo {
// Method 2
// Main driver method
public static void main(String[] args)
{
// Creating enemy list
List enemyList = new ArrayList<>();
// Creating enemy list copy
List enemyListCopy = new ArrayList<>();
// Create baseArcher
ArcherEnemy baseArcher = new ArcherEnemy();
// Setting attributes
baseArcher.health = 150;
baseArcher.speed = 35;
baseArcher.name = "Base Archer";
baseArcher.attackRange = 100;
enemyList.add(baseArcher);
// Create clone baseArcher
ArcherEnemy baseArcherClone
= (ArcherEnemy)baseArcher.clone();
// Adding clone to enemyList
enemyList.add(baseArcherClone);
// Create baseMeleeEnemy
MeleeEnemy baseMeleeEnemy = new MeleeEnemy();
// Setting attributes
baseMeleeEnemy.health = 10;
baseMeleeEnemy.speed = 20;
baseMeleeEnemy.name = "blue";
baseMeleeEnemy.blockChance = 7;
baseMeleeEnemy.withShield = true;
// Now adding baseMeleeEnemy to enemyList
// using add() method
enemyList.add(baseMeleeEnemy);
// Cloning whole list and comparing
cloneAndCompare(enemyList, enemyListCopy);
}
// Method 1
// To clone and compare
private static void
cloneAndCompare(List enemyList,
List enemyListCopy)
{
// Iterate over enemyList
// using for-each loop
for (Enemy enemy : enemyList) {
// Clone enemys and add into enemyListCopy
enemyListCopy.add(enemy.clone());
}
// Compare enemys in enemyList and in enemyListCopy
for (int i = 0; i < enemyList.size(); i++) {
// Checking that enemy and cloneEnemy have
// different links
if (enemyList.get(i) != enemyListCopy.get(i)) {
// Simply printing the result
System.out.println(
i
+ ": Enemy are different objects (yay!)");
// Check that they have same params
if (enemyList.get(i).equals(
enemyListCopy.get(i))) {
// Print statement if they are identical
System.out.println(
i
+ ": And they are identical (yay!)");
}
else { // Print statement if they are
// not-identical
System.out.println(
i
+ ": But they are not identical (booo!)");
}
}
else {
// Print statement if Shape objects are same
System.out.println(
i
+ ": Shape objects are the same (booo!)");
}
}
}
}
Step 2: We need several enemies. Let it be ArcherEnemy and MeleeEnemy
示例 2-A
Java
// Let`s create ArcherEnemy with attack range
public class ArcherEnemy extends Enemy { // Enemy is a parent
public int attackRange; // add new param
public ArcherEnemy() {
}
public ArcherEnemy(ArcherEnemy target) { // clone constructor
super(target); // first of all clone base Enemy
if (target != null) {
this.attackRange = target.attackRange; // then clone additional params
}
}
/*
Clone based on THIS enemy
*/
@Override
public Enemy clone() {
return new ArcherEnemy(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ArcherEnemy that = (ArcherEnemy) o;
return attackRange == that.attackRange ;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), attackRange);
}
}
示例 2-B
Java
// Let`s create MeleeEnemy with 'melee params'
public class MeleeEnemy extends Enemy {
public int blockChance; // add new param
public boolean withShield; // add new param
public MeleeEnemy() {}
// Create clone constructor
// clone base Enemy
// and then clone additional params
public MeleeEnemy(MeleeEnemy target)
{
super(target);
if (target != null) {
this.blockChance = target.blockChance;
this.withShield = target.withShield;
}
}
// Clone class based on current values
@Override public Enemy clone()
{
return new MeleeEnemy(this);
}
@Override public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
if (!super.equals(o))
return false;
MeleeEnemy that = (MeleeEnemy)o;
return blockChance == that.blockChance
&& withShield == that.withShield;
}
@Override public int hashCode()
{
return Objects.hash(super.hashCode(), blockChance,
withShield);
}
}
Step 3: Let’s test the creation of clones as we have basic enemies ready.
示例 3:
Java
// Java Program to Illustrate Creation of Clones
// Main class
public class Demo {
// Method 2
// Main driver method
public static void main(String[] args)
{
// Creating enemy list
List enemyList = new ArrayList<>();
// Creating enemy list copy
List enemyListCopy = new ArrayList<>();
// Create baseArcher
ArcherEnemy baseArcher = new ArcherEnemy();
// Setting attributes
baseArcher.health = 150;
baseArcher.speed = 35;
baseArcher.name = "Base Archer";
baseArcher.attackRange = 100;
enemyList.add(baseArcher);
// Create clone baseArcher
ArcherEnemy baseArcherClone
= (ArcherEnemy)baseArcher.clone();
// Adding clone to enemyList
enemyList.add(baseArcherClone);
// Create baseMeleeEnemy
MeleeEnemy baseMeleeEnemy = new MeleeEnemy();
// Setting attributes
baseMeleeEnemy.health = 10;
baseMeleeEnemy.speed = 20;
baseMeleeEnemy.name = "blue";
baseMeleeEnemy.blockChance = 7;
baseMeleeEnemy.withShield = true;
// Now adding baseMeleeEnemy to enemyList
// using add() method
enemyList.add(baseMeleeEnemy);
// Cloning whole list and comparing
cloneAndCompare(enemyList, enemyListCopy);
}
// Method 1
// To clone and compare
private static void
cloneAndCompare(List enemyList,
List enemyListCopy)
{
// Iterate over enemyList
// using for-each loop
for (Enemy enemy : enemyList) {
// Clone enemys and add into enemyListCopy
enemyListCopy.add(enemy.clone());
}
// Compare enemys in enemyList and in enemyListCopy
for (int i = 0; i < enemyList.size(); i++) {
// Checking that enemy and cloneEnemy have
// different links
if (enemyList.get(i) != enemyListCopy.get(i)) {
// Simply printing the result
System.out.println(
i
+ ": Enemy are different objects (yay!)");
// Check that they have same params
if (enemyList.get(i).equals(
enemyListCopy.get(i))) {
// Print statement if they are identical
System.out.println(
i
+ ": And they are identical (yay!)");
}
else { // Print statement if they are
// not-identical
System.out.println(
i
+ ": But they are not identical (booo!)");
}
}
else {
// Print statement if Shape objects are same
System.out.println(
i
+ ": Shape objects are the same (booo!)");
}
}
}
}
输出:
0: Enemy are different objects (yay!) // have different links
0: And they are identical (yay!) // but have same inner state
1: Enemy are different objects (yay!) // have different links
1: And they are identical (yay!) // but have same inner state
2: Enemy are different objects (yay!) // have different links
2: And they are identical (yay!) // but have same inner state
We got a list of enemies that we can copy and transfer to the client, if necessary. With this approach, we have the opportunity to use already created enemies and combine them into different groups, storing them in complex data structures.