0
点赞
收藏
分享

微信扫一扫

开发(一)

注:本文翻译自《Development For Winners》一书的开发章节的第一部分内容,原书链接:https://grofit.gitbooks.io/development-for-winners/content/development/readme.html


这可能是一个最大的章节,这一章中主要讨论设计模式的相关内容,会深入的探讨为什么有些方法会比另一些更好,以及一些特殊的技术,例如依赖注入技术和管理大型框架的方法。


这一章主要分成下面几个部分:


  • 通用的(在Web和游戏开发中都有用的东西)

  • Web(一些对Web开发更有用的东西)

  • 游戏开发(我认为你可以在此学到一些新的模式)


你可能只想研究一些与游戏开发有关的内容,并不想去了解Web世界,但是有一些绝妙的设计模式起源于Web或者App的世界,并且这些模式已经被应用于游戏开发的世界。借助于画布(译者:原文为Canvas)和WASM,Web世界开始出现越来越多的实时应用,这意味着游戏开发也将会融合到Web世界中。反正我是会阅读所有内容的,但是读哪些内容取决于你自己。


因为这本书是有关于知识共享的,因此我将会遵循“通用->Web开发->游戏开发”这条路径进行讲述,因为很多的技术在Web世界中学习会相对来说简单一些,你只需要将这些知识迁移到游戏开发世界中即可,像是在Web世界中学习MVVM框架,在我看来是比较简单的,学会后,你只需要将它们应用于游戏开发世界中即可。但是,我们将会试图在两个世界中进行讲解,以便于你能够有一个更全面的了解,以及知晓两者之间实现的差异。


通用


这一章节涵盖了可以应用于任何地方的技术和方法,可以用于Web应用,游戏甚至是手机应用。


这里有大段的带有注释的代码解释了它们是如何工作的以及为什么它们是有用的,在阅读其他章节之前,你应该先读完这一章的所有内容。


工厂模式(Factory)


工厂模式是你将接触的一个相对简单的模式。它的目的是隐藏一个类下面的创建机制,以便于你能够以一种分离的方式创建一些东西,以及获取一些你需要的实现而无需自己考虑实际的初始化操作。


开始


例如我们有一些像这样的类:

public enum AnimalType 
{
Unknown = 0,
Dog = 1,
Cat = 2
}
public interface IAnimal
{
string MakeSound();
}
public class Dog : IAnimal
{
public string MakeSound()
{ return "woof woof"; }
}
public class Cat : IAnimal
{
public string MakeSound()
{ return "meow meow"; }
}


基于上方的类型如果我们想去获得一个动物对象,我们就可以创建一个像这样的工厂:

public class AnimalFactory() 
{
public IAnimal CreateAnimal(AnimalType type)
{
switch(type)
{
case AnimalType.Dog: { return new Dog(); }
default: { return new Cat(); }
}
}
}

这是一个非常基础的工厂,它可以生成你需要的实现而你无需考虑它是如何办到的。


使用方法举例

var animalFactory = new AnimalFactory(); 
var myAnimal = animalFactory.CreateAnimal(AnimalType.Dog);
Console.WriteLine(myAnimal.MakeSound());


历史上工厂模式主要被用于硬件资源的抽象,像是纹理图片或者顶点缓冲区等。当你正在做一个跨平台的引擎需要处理OpenGL,DirectX(或者一些其他的渲染技术),开发者很少去考虑底层的硬件,他们只想要一个某一尺寸的纹理图片来使用,或者某一尺寸的顶点缓冲区用于渲染,因此这个模式可以帮助将底层需要考虑的事情抽象化,暴露高层级的对象代表底层的资源。


建造者模式(Builder)


建造者模式与工厂模式有一些重叠,它也是用来实例化一种类型的对象,不过在这一点上,它表现的更智能一些。


你需要构建一个实例而不是去请求一个实例,当你构建完成并设置好以后,你就可以调用构建方法,这个实例就会被构建出来。


假设我们有如下的类:

public class Person 
{
public string Name {get;set;}
public int Age {get;set;}
public bool IsMale {get;set;}
public List<string> Skills {get; set;}
public Person()
{
Skills = new List<string>();
}
}


接着我们想要通过建造者来创建它,我们会这样做:

public class PersonBuilder
{
private string _name;
private int _age;
private bool _isMale;
private List<string> _skills;
public PersonBuilder Create()
{
_name = string.Empty;
_age = 0;
_isMale = false;
_skills = new List<string>();
return this;
}
public PersonBuilder WithName(string name)
{
_name = name;
return this;
}
public PersonBuilder WithAge(int age)
{
_age = age;
return this;
}
public PersonBuilder IsMale(bool isMale)
{
_age = age;
return this;
}
public PersonBuilder AddSkill(string skill)
{
_skills.Add(skill);
return this;
}
public Person Build()
{
return new Person
{
Name = _name,
Age = _age,
IsMale = _isMale,
Skills = _skills
};
}
}

这看起来似乎有很多的代码,但是基本上我们只是以一种合理的方式暴露出了一些字段,接着移步到我们真正关心的地方,那只不过是一点点构建操作而已。


接着下面是几个例子:

var personBuilder = new PersonBuilder();




var maleMathTeacher = personBuilder.Create()
.WithName("Mr Green")
.WithAge(26)
.IsMale(true)
.AddSkill("math")
.AddSkill("teaching")
.Build();




var femaleGeographyTeacher = personBuilder.Create()
.WithName("Mrs Red")
.WithAge(26)
.AddSkill("geography")
.AddSkill("teaching")
.Build();


这里展示了一些基础的用法,你也可以做一些更有趣的东西,你可以为一个特殊的子集类型创建一个建造器,在不需要重置的情况下不停的创建这个子集的对象。

var preSetBuilder = personBuilder.Create()
.WithAge(18)
.AddSkill("running")
.AddSkill("healing");




var recruit1 = preSetBuilder.WithName("Bob")
.IsMale(true)
.Build();




var recruit2 = preSetBuilder.WithName("Jane")
.IsMale(false)
.Build();


这是一个需要了解的相当有用的模式,它在自动化测试或者包装一些重用的实例化逻辑时非常有用,因为你可以暴露所期望暴露的任何数据,使用一种高层级的方法,以一种特定的方式设置属性。你甚至可以增加额外的方法去设置预构建的版本。


这个模式的代码中还有一处可以借鉴的是使用"​return this"​返回当前的对象的方式,这允许你使用链式的方式将所有的逻辑串起来然后一起构建对象。这种链式的风格不论用在哪里都是很不错的。


控制反转(Inversion of Control)


它是什么?


我确定你可能听过有人在某些地方提到过控制反转(IoC),我认为对于明智的软件开发来讲,它是一个最基础的模式。你可能听人们将控制反转(IcC)和依赖注入(DI)这两个术语混用,实际上依赖注入其实是一种实现控制反转的方式,但是它们俩经常被用在一起。


不废话了,让我们直接看一下一个使用控制反转的类:

public class UsingIoC
{
private ISomeDependency _someDependency;
public UsingIoC(ISomeDependency someDependency)
{ _someDependency = someDependency; }
public void DoSomething()
{ _someDependency.MakeTheMagic(); }
}


这可能是最简单的控制反转(IoC)的例子了,我们只关注其中最重要的部分,依赖通过参数的方式传递进一个类中,而不是通过本地实例化的方式。你可能暂时不能理解上面方法的好处,让我们再看一个通常被使用的方式。

public class NotUsingIoC
{
private ISomeDependency _someDependency;
public NotUsingIoC()
{ _someDependency = new SomeDependency(); }
public void DoSomething()
{ _someDependency.MakeTheMagic(); }
}


还有另一种叫做服务定位(Service Location)的方式可以解决上方的问题,我们稍后就会提到它。


同样的类,做着同样的事情,但是这次我们没有通过它的构造函数传递任何东西,我们只是在构造函数中创建了这个依赖的实例。我知道很多人这样做是因为这种方式简单,不需要去写复杂的构造函数,只是以原子的方式使用这些的类而不需要为传递任何其他的东西而担心。然而,这个方法注定有很多的问题,让我们快速的看一下使用第二种方式你将会遇到哪些问题:


  • 你不能修改所使用的 ISomeDependency 的实现

  • 在独立测试这个类时你将会遇到困难,因为你不能为它模拟一个依赖对象

  • 如果你想要改写 ISomeDependency 的默认实现,你将不得不修改这个类中的源码


让我们再看一下原来的例子,可以看到由于我们传递了 ISomeDependency ,因此就不会再有上面的这些问题。只是通过传递依赖而不是创建依赖这点事情,就使我们在更好的设计之路上迈出了一大步,这个方法也通常被称为“好莱坞原则”,即“不要打给我,让我来打给你”。


为什么它很重要?


我上面曾提到过,想要写好单元测试,这个模式一个最基础的需求。如果不能够传递依赖,你就不能剔除那些你并不关心的测试,而只专注于测试重要的部分。它也可以使你的代码更易于修改和维护。

当你使用控制反转时,想象一下你有一个 IPathfinder 的接口,你决定制作一个  AStarPathfinder 的实现,你的路径查找算法运算速度很快,但是对于大地图来说需要消耗大量的内存。接着你做了一个 OptimizedAStarPathfinder,现在你可以在你的整个代码仓库中使用这个实现而不需要修改包含它的类的任何一行代码。


这里也提一下上面提到的依赖注入(DI),通常你将会使用依赖注入框架以一种简单的方式处理类中的所有依赖。

它可能看起来很烦人,因为不得不在构造器中手动的设置每个依赖。但是这是值得做的,一旦这我们这样做了,接着就可以涉及一些组合和单元测试的话题,因为这是实现它们的前提。 

举报

相关推荐

0 条评论