奇幻RPG(人物构造 与 Abstract Factory模式)

2024-08-29 23:48

本文主要是介绍奇幻RPG(人物构造 与 Abstract Factory模式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在前一节,我们介绍了Strategy模式,并使用此模式实现了一个根据角色的职业来分配技能的范例(实际也就是动态地为类分配方法)。作为一款奇幻RPG,有了职业,我们还应当可以为角色选择种族,比如说:人类(Human)、精灵(Elf)、矮人(Dwarf)、兽人(Orc)等等。而这四个种族又有着截然不同的外形,精灵皮肤灰白、有着长长的耳朵、没有体毛和胡须;矮人的皮肤与人类近似,但是身材矮小、通常留着浓密的胡子;兽人则有着绿色的皮肤和高大的身躯,并且面目丑陋。本文将讨论如何使用GOF的Abstract Factory抽象工厂来实现这样的角色外形设计。

面向实现的方式

简单起见,我们假设角色身体由三部分构成,分别是:头(Head)、身材(Stature)、皮肤(Skin)。那么对于人类的构造,我们的第一反应自然而然地想到:它应当由 HumanHead、HumanStature、HumanSkin三个类复合而成。对于精灵也是类似的设计,于是,我们实现了下面这样的设计(本文将仅以人类和精灵为例介绍):

抽象组成身体的实体类

我们发现这样做,每个角色与他的身体部件是牢牢绑定在一起的,每创建一个角色,我们都需要为它先行创建所有其复合的类(组成身体的实体类(Concret Class),比如HumanHead)。按照面向对象的思想,我们想到应该对这一过程进行封装,将创建角色部件这件事委派给其他的类来完成。观察上图,我们发现尽管角色不同,但它们都是由三个部分构成,所以,我们所能想到的实现这一过程的第一步,就是对组成身体的实体类进行抽象,我们定义三个接口:Head、Stature、Skin,代表身体的三个部分,并且让Human和Elf的实体类去实现这个接口:

观察上图,我们发现尽管定义了接口,但是如果角色Human和Elf仍然与接口的实体类关联,那么效果与面向实现完全相同。现在,是时候对设计做些改动,我们让Human和Elf与接口关联,而不是与接口的实现关联(OO思想:面向接口编程,而不是面向实现编程)。这一次,我们的设计变成下图:

一眼望去,我们发现的第一个问题就是:Human与Elf惊人地相似,再仔细看看,我们注意到它们除了名字不同其余的完全一样。我们不禁思考:有必要把两个完全一样的类起两个名字分别保存么?答案是没有必要,我们将它们合并成一个类,起名叫Race,设计再次变成下面这样:

创建工厂类

看到这里,我们可能会想:现在结构似乎已经很完善了,我们定义了接口来解决问题,也没有为不同的角色创建多个不同的类,而只要在Race的构造函数中为代表身体部件的变量赋不同的值,就可以创建不同种族的角色。好的,那么我们来看看如果要创建一个Human 代码需要如何编写:

Head head = new HumanHead();
Stature stature = new HumanStature();
Skin skin = new HumanSkin();
Race human = new Race(head, stature, skin);

而Race的构造函数是这样的:

public Race(head, stature, skin){
    this.head = head;
    this.stature = stature;
    this.skin = skin;
}

我们看到,仅仅创建一个类这样似乎太麻烦了,而且身体的部分类是角色的组成部分,为什么它们要先于角色创建呢?

这时候,我们想到如果有一个类可以专门负责创建身体部件这件事,当我们想要创建角色的时候,将这个类传递给Race的构造函数就可以了。我们管创建Human身体组成部分的类称作:HumanPartsFactory,创建Elf身体部分的类称作ElfPartsFacotry。那么它们应该是这样的:

现在,我们再要创建一个Human,代码变成了这样:

HumanPartsFactory humanFactory = new HumanPartsFactory();
Race Human = new Race(humanFactory);

相应的,我们的构造函数也需要改一改:

public Race(HumanPartsFactory humanFacotry){
    head = humanFactory.CreateHead();
    stature = humanFactory.CreateStature();
    skin = humanFactory.CreateSkin();
}

一切似乎都很好,直到我们需要创建一个Elf的时候... 我们发现Race的构造函数只能接受一个HumanPartsFactory类型的参数,为了传递ElfPartsFactory,我们将不得不再添加一个接受ElfPartsFactory类型的构造函数。这样显然不好,这一次,有了之前的经验,我们知道我们可以通过同样的方法来解决。

看到这里,你是否能够体会到一些“面向接口”编程的意味?注意到RacePartsFactory,它内部的方法返回的都是接口类型,而其实体子类的方法返回的都是接口的实体类。如果我们之前不声明那看似无用的接口,这里是无法实现的。

现在,我们再次修改Race的构造函数:

public Race(RacePartsFactory raceFacotry){
    head = raceFacotry.CreateHead();
    stature = raceFacotry.CreateStature();
    skin = raceFacotry.CreateSkin();
}

当我们需要一个Human的时候:

RacePartsFactory humanFactory = new HumanPartsFactory();
Race human = new Race(humanFactory);

当我们需要一个Elf的时候:

RacePartsFactory elfFactory = new ElfPartsFactory();
Race elf = new Race(elfFactory);

Abstract Factory设计模式

上面做的这些,使我们又完成了一个设计模式:Abstract Factory。它的正式定义是这样的:提供一个接口用于创建一系列相互关联或者相互依赖的对象,而不需要指定它们的实体类。

下面是本例中 Abstract Factory模式的最终图:

代码实现和测试

using System;
using System.Collections.Generic;
using System.Text;

namespace AbstractFactory {

    // 定义构成身体部分的接口
    public interface IHead {  string name { get;} }
    public interface IStature {  string name { get;} }
    public interface ISkin {  string name { get;} } 

    // 组成 Human 的类
    public class HumanHead : IHead { public string name { get { return "Human Head"; } } }
    public class HumanStature : IStature { public string name { get { return "Human Stature"; } } }
    public class HumanSkin : ISkin { public string name { get { return "Human Skin"; } } }

    // 组成 Elf 的类
   public class ElfHead : IHead { public string name { get { return "Elf Head"; } } }
    public class ElfStature : IStature { public string name { get { return "Elf Stature"; } } }
   public class ElfSkin : ISkin { public string name { get { return "Elf Skin"; } } }

    // 定义工厂接口
    public interface IRacePartsFactory {
       IHead CreateHead();
       ISkin CreateSkin();
       IStature CreateStature();
    }

    // 定义Human身体的工厂类
    public class HumanPartsFactory : IRacePartsFactory {
       public IHead CreateHead() {
           return new HumanHead();
       }
       public IStature CreateStature() {
           return new HumanStature();
       }
        public ISkin CreateSkin() {
           return new HumanSkin();
       }
    }

    // 定义Elf身体的工厂类
    public class ElfPartsFactory : IRacePartsFactory {
       public IHead CreateHead() {
           return new ElfHead();
       }
       public IStature CreateStature() {
           return new ElfStature();
        }
       public ISkin CreateSkin() {
           return new ElfSkin();
       }
    }
    
    // 定义 Race 类
    public class Race {
       public IHead Head;    // 做示范用,所以没有构建属性
       public IStature Stature;
       public ISkin Skin;

       public Race(IRacePartsFactory raceFactory) {
           Head = raceFactory.CreateHead();
           Stature = raceFactory.CreateStature();
           Skin = raceFactory.CreateSkin();
       }
    }

    class Program {
       static void Main(string[] args) {
           // 创建一个 精灵(Elf)
           Race Elf = new Race(new ElfPartsFactory());
           Console.WriteLine(Elf.Head.name);
           Console.WriteLine(Elf.Stature.name);           
           Console.WriteLine(Elf.Skin.name);
       }
    }
}

总结

本文中我们一步步学习了Abstract Factory抽象工厂模式的实现。

我首先介绍了我们奇幻RPG所面临的一个问题:我们需要创建形态各异的角色。随后,我们通过面向实现的方式来完成了这一过程,并讨论了它的不足。随后,我们先通过接口的使用对种族进行了抽象。接着,我们由将创建角色组成部分类的过程进行了封装,将这一过程委派给了工厂类。最后,我们又对工厂类进行了抽象,最终实现了Abstract Factory工厂模式。

希望本文能给你带来帮助。

这篇关于奇幻RPG(人物构造 与 Abstract Factory模式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1119194

相关文章

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

leetcode105 从前序与中序遍历序列构造二叉树

根据一棵树的前序遍历与中序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如,给出 前序遍历 preorder = [3,9,20,15,7]中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树: 3/ \9 20/ \15 7   class Solution {public TreeNode buildTree(int[] pr

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停