构建一个具有 Emscripten 依赖项的项目

在这篇文章中,我将描述使用 Emscripten 构建一个由多个库组成的项目。
目前,Emscripten 不支持构建共享库,因此第一步是将所有库从 Shared 转移到 Static。 Emscripten 使用自己的包含文件,因此需要通过将符号链接从系统目录转发到 Emscripten 工具链来解决头文件的可见性问题:

ln -s /usr/local/include/FlameSteelFramework $EMSDK/fastcomp/emscripten/system/include/FlameSteelFramework

如果您使用的是 CMake,则需要在 add_library 方法的 CMakeLists.txt 文件中更改 SHARED->STATIC。您可以使用以下命令构建库/应用程序以进行进一步的静态链接:

emcmake cmake .
emmake make

接下来,您需要构建主应用程序,并在链接阶段指定 *.a 库文件。我无法指定相对路径;只有在 CMakeLists.txt 文件中指定完整路径后,构建才能正确完成:

elseif(EMSCRIPTEN)
target_link_libraries(${FSEGT_PROJECT_NAME} GL GLEW 
/home/demensdeum/Sources/cube-art-project-bootstrap/cube-art-project/sharedLib/libCubeArtProject.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkitFSGL/libFlameSteelEngineGameToolkitFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkit/libFlameSteelEngineGameToolkit.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/libFlameSteelCore.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/libFlameSteelBattleHorn.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FSGL/libFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/libFlameSteelCommonTraits.a)
else()

来源

https://emscripten.org/文档/compiling/Building-Projects.html#using-libraries

共享库 CMake C++

我最近决定将 FlameSteelFramework 的所有部分设为单独的共享库,然后我将展示 FlameSteelCore

cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

CMake 执行的命令:将 src/FlameSteelCore/ 目录中所有带有 *.cpp 扩展名的文件收集到共享库中,将 src/FlameSteelCore 中带有 *.h 扩展名的所有标头复制到 include/FlameSteelFramework (在我的例子中)这是/usr/local/include/FlameSteelFramework),将共享lib复制到lib目录(/usr/local/lib)
安装后,可能需要更新LD缓存– sudo ldconfig。
要在 Ubuntu 上构建并安装(如果您有正确的构建工具链),只需运行以下命令:

cmake . && make && sudo make install

为了测试安装过程,我将 make 前缀传递到本地文件夹 makeInstallTestPlayground:

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

参考文献

https: //stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install

C++语言解释器–紧贴

不久前,我遇到了一个有趣的项目,名为Cling,它是一个 C++ 语言解释器,可以从控制台交互工作,等等。您可以通过以下链接查看该项目: https://github.com/root -项目/坚持
Ubuntu 的安装很简单 –下载所需版本的存档,解压它,转到 bin 文件夹并在终端中运行 cling。
下面是加载库 FlameSteelCore、初始化对象、打印 id 的示例:

丢失 Emscripten 异常和正则表达式问题

丢失异常

Emscripten 的一个有趣功能:当通过 emscripten_set_main_loop 启动游戏循环时,您应该记住必须在循环方法中直接通过 try catch 重新添加异常处理,因为运行时丢失来自外部的 try catch 块。
最简单的方法是使用 javascript 警报在浏览器中显示错误文本:

            catch (const std::exception &exc)
            {
                const char *errorText = exc.what();
                cout << "Exception: " << errorText << "; Stop execution" << endl;

                EM_ASM_(
                {
                    var errorText = UTF8ToString($0);
                    alert(errorText);

                }, errorText);

                abort();

正则表达式太复杂

如果 regex 的 std 实现认为正则表达式过于复杂,则可能会抛出 error_complexity 异常。目前emscripten的实现中就出现了这种情况,所以我建议您通过正则表达式来实现解析测试,或者使用第三方正则表达式实现。

模式生成器

Builder 模式属于一组模式,其存在对我来说不是特别清楚,我注意到它明显的冗余。属于生成设计模式组。用于实现创建复杂对象的简单接口。

适用性

接口的简化。可以更方便地在带有大量参数的构造函数中创建对象,客观上提高了代码的可读性。

不带构建器的 C++ 示例:

auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:

let monster = Monster(weapon: “Claws”, health: 100)

不变性。使用构建器,您可以确保创建的对象的封装,直到最终组装阶段。在这里,您需要仔细考虑使用模式是否能让您摆脱工作环境的高度动态性,也许使用模式不会带来任何好处,因为开发团队中缺乏使用封装的文化; .

在对象创建的不同阶段与组件交互。同样使用该模式,可以确保在与系统的其他组件交互时逐步创建对象。这很可能非常有用(?)

批评

当然,您需要“仔细”考虑是否值得在您的项目中广泛使用该模式。具有现代语法和高级 IDE 的语言在提高代码可读性方面消除了使用生成器的需要(请参阅有关命名参数的要点)
1994 年 GoF 书出版时是否应该使用这种模式?很可能是的,但是,从那些年的开源代码库来看,很少有人使用它。

来源

https://refactoring.guru/ru/design-patterns/builder

图案复合

Composite模式是指结构设计模式;在国内被称为“Compositor”。
假设我们正在开发一个应用程序 –相册。用户可以创建文件夹、在其中添加照片以及执行其他操作。您肯定需要能够显示文件夹中的文件数量、所有文件和文件夹的总数。
显然需要使用树,但是如何以简单方便的接口实现树架构呢?复合模式来救援。

希拉在月鸭

接下来在目录中我们实现 dataCount() 方法 –通过遍历组件数组中的所有元素,将它们的所有 dataCount 相加。
一切准备就绪!
下面是 Go 中的示例:
package main

import "fmt"

type component interface {

dataCount() int

}

type file struct {

}

type directory struct {

c []component

}

func (f file) dataCount() int {

return 1

}

func (d directory) dataCount() int {

var outputDataCount int = 0

for _, v := range d.c {
outputDataCount += v.dataCount()
}

return outputDataCount

}

func (d *directory) addComponent(c component) {

d.c = append(d.c, c)

}

func main() {

var f file
var rd directory
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)

fmt.Println(rd.dataCount())

var sd directory
sd.addComponent(f)

rd.addComponent(sd)
rd.addComponent(sd)
rd.addComponent(sd)

fmt.Println(sd.dataCount())
fmt.Println(rd.dataCount())

}

来源

https://refactoring.guru/ru/design-patterns/复合

模式适配器

本杰明·努涅斯·冈萨雷斯

适配器模式是指结构设计模式。

适配器提供两个类/接口之间的数据/接口转换。

假设我们正在开发一个基于神经网络来确定买家在商店中的目标的系统。该系统接收来自商店摄像机的视频流,根据顾客的行为识别顾客,并将他们分类。团体的类型来买东西(潜在买家),只是来看一看(围观者),来偷东西(小偷),来退货(不满意的买家),来喝醉了/喝醉了(潜在的吵闹)。

像所有经验丰富的开发人员一样,我们找到了一个现成的神经网络,可以根据视频流对笼子里的猴子种类进行分类,柏林动物园动物研究所免费提供了该网络,并在视频流上对其进行重新训练从商店购买并获得一个可用的最先进的系统。

有一个小问题–视频流以mpeg2格式编码,我们的系统仅支持OGG Theora。我们没有系统的源代码,我们唯一能做的就是–更改数据集并训练神经网络。该怎么办?编写一个适配器类,用于传输来自 mpeg2 -> OGG Theora 的流并将其发送到神经网络。

根据经典方案,该模式涉及客户端、目标、适应者和适配器。本例中的客户端是一个神经网络,它接收 OGG Theora 中的视频流,目标为“”。与其交互的接口,adaptee –输出mpeg2视频流的接口,适配器–将 mpeg2 转换为 OGG Theora 并通过目标接口发送。

一切看起来都很简单吗?

来源

https://ru.wikipedia.org/wiki/Adapter_ (设计模式)
https://refactoring.guru/ru/design-patterns/adapter

委托模式

委托模式是主要的设计模式之一。
假设我们正在开发一个理发店应用程序。该应用程序有一个日历,用于选择记录的日期;点击日期将打开一个可供选择的理发师列表。
让我们实现一个简单的系统组件链接,使用指向彼此的指针将日历和屏幕结合起来,实现列表显示:


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

几天后,要求发生变化;在显示列表之前,您需要显示包含多种服务选择(胡须修剪等)的优惠,但并非总是如此,除了周六之外的所有日子。
我们向日历添加一个检查是否是星期六的检查,根据它,我们调用理发师列表或服务列表的方法,为了清楚起见,我将演示:


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

一周后,我们被要求在反馈屏幕上添加日历,就在那时,第一个架构问题发生了!
该怎么办?日历与理发预约屏幕紧密相连。
哇!啊!哦哦
如果您继续使用这个疯狂的应用程序架构,您应该复制整个日历类并将此副本与反馈屏幕相关联。
好吧,看起来不错,然后我们又添加了几个屏幕和几份日历,然后 X 时刻就来了。我们被要求更改日历的设计,这意味着现在我们需要找到日历的所有副本并对所有副本添加相同的更改。这种“做法”极大地影响了开发速度,也增加了出错的机会。结果,这样的项目最终会陷入崩溃状态,甚至原始架构的作者也不再理解他的类的副本是如何工作的,并且沿途添加的其他黑客也会立即崩溃。
需要做什么,或者更好的是,现在开始做什么还不算太晚?使用委托模式!
委托是一种通过公共接口传递类事件的方法。下面是日历委托的示例:

protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

现在让我们将使用委托的代码添加到示例代码中:


// псевдокод

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

这样一来,我们就将日历与屏幕完全解开了,当从日历中选择日期时,它会传输日期选择事件– *将事件处理委托给订阅者;订阅者就是屏幕。
我们从这种方法中得到什么好处?现在我们可以独立地更改日历和屏幕逻辑,而无需重复类,从而简化了进一步的支持;这样就实现了系统组件实现的“全责原则”,遵守了DRY原则。
使用委托时,您可以添加、更改显示窗口的逻辑、屏幕上任何内容的顺序,这完全不会影响日历和其他类,客观上它们不应该参与与它们不直接相关的进程。< br/>或者,不太打扰自己的程序员可以使用通过公共总线发送消息,而不需要编写单独的协议/委托接口,在这种情况下最好使用委托。我在上一篇文章中写过这种方法的缺点– “观察者模式。”

来源

https://refactoring.guru/ru/replace-inheritance -with-delegation

死亡面具1.0!

今天发布了游戏 Death-Mask 的发布版本,它是与 Flame Steel 引擎、Flame Steel 引擎游戏工具包和其他库一起从头开始创建的。

https://demensdeum.com/games/deathMask/< /p>

在游戏中,你将扮演一个名叫 Revil-Razorback 的家伙,他想要拥有一件能够赋予无尽生命的神器——“Revil-Razorback”。死亡面具。四处寻找,你会无数次死于无人机之手,直到找到你要找的东西。

在不久的将来可能会更新 3D 模型并进行微小的游戏更改。

死亡面具–在无尽的技术迷宫中进行网络奇幻冒险。准备好去死吧!

观察者模式

观察者模式是指行为设计模式。
该模式允许您使用通用接口将对象状态的更改发送给订阅者。
假设我们正在为程序员开发一个信使,我们在应用程序中有一个聊天屏幕。当您收到包含“问题”和“错误”或“出现问题”文本的消息时,您需要将错误列表屏幕和设置屏幕设置为红色。
接下来,我将描述解决该问题的两种选择,第一种很简单,但极难支持,第二种支持更稳定,但需要你在最初的实施过程中转过头来。

公共巴士

该模式的所有实现都包含数据更改时发送消息、订阅消息以及方法中的进一步处理。共享总线选项包含一个将消息分派给接收者的单个对象(通常是单例)。
实现的简单性如下:

  1. 对象向共享总线发送抽象消息
  2. 订阅共享总线的另一个对象捕获该消息并决定是否处理它。

Apple 提供的实现选项之一(NSNotificationCenter 子系统)添加了消息头与收件人在传递时调用的方法名称的匹配。
这种方法的最大缺点是——如果您进一步更改消息,则需要首先记住并手动编辑处理和发送该消息的所有位置。有一种情况是快速初始实施,然后是长期、复杂的支持,需要知识库才能正确操作。

多播委托

在这个实现中,我们将创建最终的多播委托类;就像共享总线的情况一样,对象可以订阅它来接收“消息”或“事件”,但解析和过滤消息的工作是未分配给对象。相反,订阅者类必须实现委托的多播方法来通知它们。
这是通过使用委托接口/协议来实现的;当通用接口发生变化时,应用程序将不再构建,此时需要重做所有处理给定消息的地方,而不需要维护单独的知识库记住这些地方。 编译器是你的朋友。
这种方法提高了团队的生产力,因为不需要编写或存储文档,新开发人员不需要尝试理解消息及其参数是如何处理的,而是使用方便且易于理解的界面,这就是通过代码实现文档范式的方式。
多播委托本身基于委托模式,我将在下一篇文章中介绍它。

来源

https://refactoring.gu/ru/design-patterns/observer

代理模式

代理模式是指结构设计模式。
该模式描述了通过类层处理类的技术——代理人。代理允许您更改原始类的功能,并且能够保留原始行为,同时维护原始类接口。
让我们想象一下这种情况– 2015年,西欧国家之一决定记录该国用户网站的所有请求,以改进统计并深入了解公民的政治情绪。
让我们想象一下公民用来访问互联网的网关的简单实现的伪代码:

class InternetRouter {

    private let internet: Internet

    init(internet: Internet) {
        self.internet = internet
    }

    func handle(request: Request, from client: Client) -> Data {
        return self.internet.handle(request)
    }

}

在上面的代码中,我们创建了一个 Internet 路由器类,其中包含一个指向提供 Internet 访问的对象的指针。当客户发出网站请求时,我们会从互联网返回响应。

使用代理模式和单例反模式,我们将添加用于记录客户端名称和 URL 的功能:

class InternetRouterProxy {

    private let internetRouter: InternetRouter

    init(internet: Internet) {
        self.internetRouter = InternetRouter(internet: internet)
    }

    func handle(request: Request, from client: Client) -> Data {

        Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)

        return self.internetRouter.handle(request: request, from: client)
    }

}

由于代理类InternetRouterProxy中保留了原始InternetRouter接口,因此只需将InternerRouter中的初始化类替换为其代理即可,无需进一步更改代码库。

来源

https://refactoring.guru/ru/design-patterns/代理

死亡面具狂野测试版

《Death-Mask》游戏进入公测状态(野测)
游戏的主菜单屏幕已重新设计,添加了科技迷宫的蓝色区域的视图,并有令人愉悦的背景音乐。

接下来,我计划重新设计游戏控制器,添加像旧射击游戏一样的流畅移动,盒子、武器、敌人的高质量 3D 模型,不仅通过门户移动到技术迷宫的其他级别的能力(电梯、门、从地板上的洞掉下来、墙上的洞掉下来),我将为生成的迷宫的环境添加一点变化。我还将致力于游戏平衡。
骨骼动画将作为发布前的完善阶段添加。< /p>

图案原型

原型模式属于生成设计模式组。
假设我们正在开发约会应用程序 Tender,根据我们的商业模式,我们有一个付费机会来复制您自己的个人资料,自动更改姓名和照片的顺序。这样做是为了让用户有机会在应用程序中同时维护与不同朋友组的多个个人资料。
通过点击创建个人资料副本的按钮,我们需要实现复制个人资料、自动生成姓名以及重新排序照片。
朴素的伪代码实现:

fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

现在让我们想象一下其他团队成员复制粘贴了复制代码或从头开始想出它,然后添加了一个新字段–喜欢。该字段存储个人资料喜欢的数量,现在您需要通过添加新字段来手动更新*所有*发生复制的位置。代码的维护和测试非常耗时且困难。
为了解决这个问题,原型设计模式被发明了。让我们创建一个通用的复制协议,其中的 copy() 方法返回具有必要字段的对象的副本。更改实体字段后,您只需要更新一个 copy() 方法,而不需要手动搜索并更新所有包含复制代码的地方。

来源

https://refactoring.guru/ru/design-patterns/prototype

状态机和模式条件

在这篇文章中我将描述状态机(State Machine)的使用,展示一个简单的实现,一个使用State模式的实现。值得一提的是,如果状态少于三个,则不宜使用 State 模式,因为这通常会导致代码可读性和相关支持问题不必要的复杂性——凡事都要有个度。

MEAACT PHOTO / STUART PRICE.

旗帜之王

假设我们正在为民用飞机的媒体系统开发视频播放器屏幕,播放器必须能够加载视频流、播放视频、允许用户停止下载过程、倒带以及执行其他通常的操作一名玩家。
假设播放器缓存了视频流的下一个块,检查是否有足够的块用于播放,开始向用户播放片段,同时继续下载下一个。
此时,用户快退到视频的中间,即现在需要停止播放当前片段并从新位置开始加载。然而,在某些情况下这是无法做到的——用户在观看有关航空安全的视频时无法控制视频流的播放。让我们检查 isSafetyVideoPlaying 标志来检查这种情况。
系统还必须能够暂停当前视频并通过播放器广播船长和船员的警报。让我们添加另一个 isAnnouncementPlaying 标志。另外,要求在显示有关使用播放器的帮助时不要暂停播放,另一个标志是HelpPresenting。

媒体播放器示例伪代码:

class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

由于高可变性(熵),上面的示例难以阅读且难以维护。此示例基于我使用不使用状态机的*许多*项目的代码库的经验。
每个复选框必须专门“控制”应用程序的界面元素和业务逻辑;添加另一个复选框的开发人员必须能够处理它们,使用所有可能的选项多次检查和重新检查所有内容。
代入公式“2 ^ 复选框数量”,只需 6 个复选框即可获得 2 ^ 6 = 64 个应用程序行为选项,所有这些复选框组合都需要手动检查和维护。
从开发人员的角度来看,使用这样的系统添加新功能如下所示:
–我们需要添加显示航空公司浏览器页面的功能,并且如果机组人员宣布某些内容,它应该像电影一样最小化。
–好的,我会做的。 (哦该死,我必须添加另一个标志并仔细检查标志相交的所有位置,有很多东西需要更改!)

这也是标志系统的一个弱点–更改应用程序的行为。很难想象如何快速/灵活地基于标志更改行为,如果只更改一个标志后您必须仔细检查所有内容。这种开发方法会导致很多问题、时间和金钱的损失。

进入机器

如果您仔细查看这些标志,您就会明白,实际上我们正在尝试处理现实世界中发生的特定进程。我们列出了它们:正常模式、显示安全视频、广播船长或船员的消息。对于每个进程,一组已知的规则会改变应用程序的行为。
根据状态机(state machine)模式的规则,我们将所有进程列为枚举中的状态,将状态这样的概念添加到播放器代码中,通过删除标志上的组合来实现基于状态的行为。这样我们就可以将测试的选项减少到精确的状态数量。

伪代码:

enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

标志系统和状态机的巨大区别在于 set(state: ..) 方法中的逻辑状态切换漏斗,它允许你将人类对状态的理解转化为程序代码,而无需玩逻辑当进一步的代码支持时将标志转换为状态的游戏。

模式状态

接下来我将展示状态机的简单实现和状态模式之间的区别。假设我们需要添加 10 个状态;结果,状态机类将增长到一个 godobject 的大小,这将变得难以维护且成本高昂。当然,这个实现比flag实现要好(使用flag系统,开发者会先开枪自杀,如果没有,那么看到2^10=1024个变体,QA就会挂掉自己,但如果他们都*不注意*任务的复杂性,那么应用程序简单的用户会注意到它将拒绝使用特定的标志组合)
如果状态数量较多,就需要使用State模式。
让我们向状态协议添加一组规则:

protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

让我们将一组规则的实现移至单独的状态,例如一个状态的代码:

class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

接下来,让我们创建一个每个状态都可以工作的上下文,并集成状态机:

final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	остальные возможные события
}

应用程序组件通过公共方法与上下文一起工作;状态对象本身决定使用上下文内的状态机从哪个状态转换到哪个状态。
因此,我们实现了上帝对象分解,维护变化的状态会容易得多,这得益于编译器跟踪协议的变化,由于代码行数的减少而降低了理解状态的复杂度,并且专注于解决特定的状态问题。您现在还可以在团队中共享工作,为团队成员提供特定状态的实现,而不必担心需要“解决”冲突,这种情况在使用一个大型状态机类时会发生。

来源

https://refactoring.guru/ru/design-patterns/state

骨骼动画(第 1 部分–着色器)

在本文中,我将描述我对骨骼动画的理解,骨骼动画在所有现代 3D 引擎中用于动画角色、游戏环境等。
我将从最具体的部分开始描述——顶点着色器,因为整个计算路径,无论多么复杂,最终都会将准备好的用于显示的数据传输到顶点着色器。

骨骼动画经过CPU处理后,进入顶点着色器。
让我提醒一下没有骨骼动画的顶点的公式:
gl_Position = 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点;
对于那些不明白这个公式是如何产生的人,您可以阅读我的文章,该文章描述了在 OpenGL 上下文中使用矩阵显示 3D 内容的原理。
对于其余的–骨骼动画的实现公式:
” vec4animatedVertex=bone0matrix*顶点*bone0weight+”
“骨骼1矩阵 * 顶点 * 骨骼1权重 +”
“骨骼2矩阵 * 顶点 * 骨骼2权重 +”
“骨骼3矩阵 * 顶点 * 骨骼3权重;\n”
” gl_Position = 投影矩阵 * 视图矩阵 * 模型矩阵 * 动画顶点;\n”

也就是说,我们将最终的骨骼变换矩阵乘以顶点以及该矩阵相对于顶点的权重。每个顶点可以由4个骨骼进行动画,冲击的强度由骨骼权重参数调节,冲击的总和应等于1。
如果影响顶点的骨骼少于 4 个怎么办?我们需要将它们之间的权重分开,并使其余的影响为零。
从数学上讲,权重乘以矩阵称为“矩阵标量乘法”。乘以一个标量可以让您总结矩阵对结果顶点的影响。

骨骼变换矩阵本身作为数组传输。 此外,该数组包含整个模型的矩阵,而不是单独的每个网格的矩阵。

但是对于每个顶点,以下信息是单独传输的:
–影响顶点的矩阵索引
–影响顶点的矩阵权重
传输多于一根骨骼,通常使用顶点上4根骨骼的效果。
另外,4 个骰子的权重之和必须始终等于 1。
接下来我们看看它在着色器中的样子。
矩阵数组:
“统一 mat4 骨骼矩阵[kMaxBones];”

有关每个顶点上 4 个骨骼的效果的信息:
“属性 vec2bon0info;”
“属性 vec2bon1info;”
“属性 vec2骨头2info;”
“属性 vec2bon3info;”

vec2 –在 X 坐标中,我们存储骨骼的索引(并在着色器中将其转换为 int),在 Y 坐标中,我们存储骨骼对顶点的影响的权重。为什么必须以二维向量传输这些数据?因为 GLSL 不支持将具有有效字段的 C 可读结构传递给着色器。

下面我将给出一个从向量中获取必要信息以进一步代入animatedVertex公式的示例:

“int boone0Index = int(bone0info.x);”
“浮动骨头0权重=骨头0info.y;”
“mat4骨头0矩阵=骨头Matrices[bone0Index];”

“int boone1Index = int(bone1info.x);”
“浮动骨头1权重=骨头1info.y;”
“mat4骨1矩阵=骨头Matrices[bone1Index];”

“int骨头2Index = int(bone2info.x);”
“浮动骨头2权重=骨头2info.y;”
“mat4骨2矩阵=骨头Matrices[bone2Index];”

“int骨头3Index = int(bone3info.x);”
“浮动骨头3权重=骨头3info.y;”
“mat4骨头3矩阵=骨头Matrices[bone3Index];”

现在 CPU 上填充的顶点结构应如下所示:
x、y、z、u、v、bone0index、bone0weight、bone1index、bone1weight、bone2index、bone2weight、bone3index、bone3weight

顶点缓冲区结构在模型加载期间填充一次,但变换矩阵会在每个渲染帧从 CPU 传输到着色器。

在剩下的部分中,我将描述在CPU上计算动画的原理,在将其传输到顶点着色器之前,我将描述骨骼节点树,遍历动画-模型-节点-网格层次结构,矩阵插值。

来源

http://ogldev.atspace.co。英国/www/tutorial38/tutorial38.html

源代码

https://gitlab.com/demensdeum/skeletal-animation

模板法

模式方法指的是行为设计​​模式。该模式描述了一种按需替换类的部分逻辑的方法,而对于后代而言,整个部分保持不变。

Cuban Cars

假设我们正在开发一家客户银行,请考虑开发授权模块的任务——用户必须能够使用抽象登录数据登录应用程序。
授权模块必须是跨平台的,支持不同的授权技术,存储不同平台的加密数据。为了实现该模块,我们选择跨平台的Kotlin语言,使用授权模块的抽象类(协议),我们将为MyPhone手机编写一个实现:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    abstract fun authorize(authorizationData: AuthorizationData)
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
    override fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

现在,对于每个手机/平台,我们都必须复制用于向服务器发送授权的代码,这违反了 DRY 原则。上面的例子很简单,在更复杂的类中会有更多的重复。为了消除代码重复,您应该使用模板方法模式。
让我们将模块的公共部分移至不可变的方法中,并将加密数据传输的功能转移到特定的平台类:

class MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage {
    fun loginAndPassword() : Pair {
        return Pair("admin", "qwerty65435")
    }
}

class ServerApiClient {
    fun authorize(authorizationData: AuthorizationData) : Unit {
        println(authorizationData.login)
        println(authorizationData.password)
        println("Authorized")
    }
}

class AuthorizationData {
    var login: String? = null
    var password: String? = null
}

interface AuthorizationModule {
    abstract fun fetchAuthorizationData() : AuthorizationData
    
    fun authorize(authorizationData: AuthorizationData) {
        ServerApiClient().authorize(authorizationData)
    }
}

class MyPhoneAuthorizationModule: AuthorizationModule {
    
    override fun fetchAuthorizationData() : AuthorizationData {
        val loginAndPassword = MyPhoneSuperDuperSecretMyPhoneAuthorizationStorage().loginAndPassword()
        val authorizationData = AuthorizationData()
        authorizationData.login = loginAndPassword.first
        authorizationData.password = loginAndPassword.second
        
        return authorizationData
    }
    
}

fun main() {
    val authorizationModule = MyPhoneAuthorizationModule()
    val authorizationData = authorizationModule.fetchAuthorizationData()
    authorizationModule.authorize(authorizationData)
}

来源

https://refactoring.guru/ru/design-模式/模板方法

源代码

https://gitlab.com/demensdeum/patterns/< /p>

图案桥

桥接模式是指结构设计模式。它允许您通过将逻辑移动到单独的抽象类中来抽象类逻辑的实现。听起来很简单,对吧?

假设我们实现一个垃圾邮件机器人,它应该能够向不同类型的信使发送消息。
我们使用通用协议来实现它:

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class SpamBot {
    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            iSeekUMessennger.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

现在让我们想象一下,用于为 iSekU Messenger 发送消息的新的、更快的协议的发布。要添加新协议,您需要复制 iSekU 机器人的实现,仅更改其中的一小部分。如果只改变了一小部分类逻辑,则不清楚为什么要这样做。这种做法违反了 DRY 原则;随着产品的进一步开发,新功能的实现会出现错误和延迟,从而体现出缺乏灵活性。
让我们将协议的逻辑转移到一个抽象类中,从而实现桥接模式:

protocol User {
    let token: String
    let username: String
}

protocol Messenger {
    var authorize(login: String, password: String)
    var send(message: String, to user: User)
}

protocol MessagesSender {
    func send(message: String, to user: User)
}

class iSeekUUser: User {
    let token: String
    let username: String
}

class iSeekUFastMessengerSender: MessagesSender {
    func send(message: String, to user: User) {
        requestSender?.perform(requestFactory.messageRequest(message: message, to: user)
    }
}

class iSeekU: Messenger {

    var authorizedUser: User?
    var requestSender: RequestSender?
    var requestFactory: RequestFactory?
    var messagesSender: MessengerMessagesSender?

    func authorize(login: String, password: String) {
        authorizedUser = requestSender?.perform(requestFactory.loginRequest(login: login, password: password))
    }
    
    func send(message: String, to user: User) {
        messagesSender?.send(message: message, to: user)
    }
}

class SpamBot {

    var messagesSender: MessagesSender?

    func start(usersList: [User]) {
        let iSeekUMessenger = iSeekU()
        iSeekUMessenger.authorize(login: "SpamBot", password: "SpamPassword")
        
        for user in usersList {
            messagesSender.send(message: "Hey checkout demensdeum blog! http://demensdeum.com", to: user)
        }
    }
}

这种方法的优点之一无疑是能够通过编写实现抽象逻辑的插件/库来扩展应用程序的功能,而无需更改主应用程序的代码。
与策略模式有什么区别?两种模式非常相似,但是,策略描述了切换*算法*,而桥接允许您切换大部分*任何复杂逻辑*。

来源

https://refactoring.guru/ru/design-patterns/bridge

源代码

https://gitlab.com/demensdeum/patterns/ p>

责任链模式

责任链是指行为设计模式。


Ganna Dolbieva

电影公司 Jah-Pictures 制作了一部关于利比里亚共产主义拉斯塔法里教徒的纪录片,名为《马利的红色黎明》。这部电影很长(8小时),很有趣,但在上映之前,事实证明,在一些国家,电影中的镜头和短语可能被认为是异端邪说,不会获得发行许可。电影制片人决定手动和自动地从电影中删除包含可疑短语的时刻。需要进行双重检查,以免在人工检查和安装过程中出现错误时,经销商代表不会在某些国家被简单枪决。
国家分为四组:没有审查制度的国家,有中等、中等和非常严格的审查制度。决定使用神经网络对观看的电影片段中的异端级别进行分类。对于该项目,需要购买非常昂贵的最先进的神经元,并针对不同级别的审查进行训练,这是开发人员的任务 -将影片分成碎片,并通过一系列神经网络传输它们,从自由到严格,直到其中一个检测到异端,然后将片段转移到人工审查以进一步编辑。不可能通过所有神经元,因为他们的工作需要太多的计算能力(毕竟我们还要支付电费),在第一个工作时停止就足够了。
朴素的伪代码实现:

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier) {
        self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        return hnnclassifier.shouldBeCensored(movieChunk)
    }
}

let lightCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"))
let normalCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal"))
let hardCensorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard"))

let classifiers = [lightCensorshipClassifier, normalCensorshipClassifier, hardCensorshipClassifier]

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    for classifier in classifiers {
        if classifier.shouldBeCensored(chunk) == true {
            print("Should censor movie chunk: \(chunk), reported by \(classifier)")
        }
   }
}

一般来说,使用分类器数组的解决方案并没有那么糟糕,但是!让我们想象一下,我们无法创建一个数组,我们有机会只创建一个分类器实体,它已经确定了电影片段的审查类型。在开发扩展应用程序功能的库(插件)时,此类限制是可能的。
让我们使用装饰器模式–让我们将链中下一个分类器的引用添加到分类器类中,并在第一个成功分类时停止验证过程。
因此,我们实现了责任链模式:

import StateOfArtCensorshipHLNNClassifiers

protocol MovieCensorshipClassifier {
    func shouldBeCensored(movieChunk: MovieChunk) -> Bool
}

class CensorshipClassifier: MovieCensorshipClassifier {

    let nextClassifier: CensorshipClassifier?
    let hnnclassifier: StateOfArtCensorshipHLNNClassifier

    init(_ hnnclassifier: StateOfArtCensorshipHLNNClassifier, nextClassifier: CensorshipClassifiers?) {
            self.nextClassifier = nextClassifier
            self.hnnclassifier = hnnclassifier
    }
    
    func shouldBeCensored(_ movieChunk: MovieChunk) -> Bool {
        let result = hnnclassifier.shouldBeCensored(movieChunk)
        
        print("Should censor movie chunk: \(movieChunk), reported by \(self)")
        
        if result == true {
                return true
        }
        else {
                return nextClassifier?.shouldBeCensored(movieChunk) ?? false
        }
    }
}

let censorshipClassifier = CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("light"), nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("normal", nextClassifier: CensorshipClassifier(StateOfArtCensorshipHLNNClassifier("hard")))))

let movie = Movie("Red Jah rising")
for chunk in movie.chunks {
    censorshipClassifier.shouldBeCensored(chunk)
}

参考文献

https://refactoring.guru/ru/设计模式/责任链

源代码

https://gitlab.com/demensdeum/patterns/< /p>

图案装饰器

Decorator模式是指结构设计模式。

装饰器用作继承的替代方法来扩展类的功能。
有一项任务是根据产品类型扩展应用程序的功能。客户需要三种类型的产品——基础、专业、终极。
基本的–统计字符数,专业–功能 基本+以大写字母打印文本,终极&#8211;基本+专业+打印文字“ULTIMATE”。
我们使用继承来实现它:

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

现在有需求实现“极致之光”产品——基本版 + 旗舰版,但没有专业版的功能。第一个发生是因为……您必须为这样一个简单的任务创建一个单独的类并复制代码。
让我们继续使用继承来实现:

protocol Feature {
	func textOperation(text: String)
}

class BasicVersionFeature: Feature {
	func textOperation(text: String) {
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: ProfessionalVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

class UltimateLightVersionFeature: BasicVersionFeature {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")	
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = UltimateLightVersionFeature()
ultimateLightProduct.textOperation(text: textToFormat)

为了清楚起见,可以进一步开发该示例,但即使现在,支持基于继承基础的系统的复杂性也是可见的——麻烦且缺乏灵活性。
装饰器是一组描述功能的协议,是一个抽象类,其中包含对扩展功能的装饰器类的子具体实例的引用。
让我们使用以下模式重写上面的示例:

protocol Feature {
	func textOperation(text: String)
}

class FeatureDecorator: Feature {
	private var feature: Feature?
	
	init(feature: Feature? = nil) {
		self.feature = feature
	}
	
	func textOperation(text: String) {
		feature?.textOperation(text: text)
	}
}

class BasicVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.count)")
	}
}

class ProfessionalVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("\(text.uppercased())")
	}
}

class UltimateVersionFeature: FeatureDecorator {
	override func textOperation(text: String) {
		super.textOperation(text: text)
		print("ULTIMATE: \(text)")
	}
}

let textToFormat = "Hello Decorator"

let basicProduct = BasicVersionFeature(feature: UltimateVersionFeature())
basicProduct.textOperation(text: textToFormat)

let professionalProduct = ProfessionalVersionFeature(feature: UltimateVersionFeature())
professionalProduct.textOperation(text: textToFormat)

let ultimateProduct = BasicVersionFeature(feature: UltimateVersionFeature(feature: ProfessionalVersionFeature()))
ultimateProduct.textOperation(text: textToFormat)

let ultimateLightProduct = BasicVersionFeature(feature: UltimateVersionFeature())
ultimateLightProduct.textOperation(text: textToFormat)

现在我们可以创建任何类型产品的变体——在应用程序启动阶段初始化组合类型就足够了,下面的例子是Ultimate + Professional版本的创建:

ultimateProfessionalProduct.textOperation(text: textToFormat)

来源

https://refactoring.guru/ru/design-patterns/decorator

源代码

https://gitlab.com/demensdeum/patterns

模式中介者

中介者模式指的是行为设计​​模式。

有一天,您收到一份开发笑话应用程序的订单 –用户按下屏幕中间的按钮,就会听到鸭子嘎嘎的有趣声音。
上传到应用商店后,该应用程序大受欢迎:每个人都在浏览你的应用程序,埃隆·马斯克在下一次火星超高速隧道启动时在他的 Instagram 上嘎嘎叫,希拉里·克林顿在辩论中击败了唐纳德·特朗普并赢得乌克兰选举,成功!
应用程序的简单实现如下所示:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

接下来,您决定添加狗吠的声音,为此您需要显示两个用于选择声音的按钮:与一只鸭子和一只狗。让我们创建两个按钮类:DuckButton 和 DogButton。
更改代码:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

再一次成功后,我们添加了猪叫声,已经有三类按钮了:

class DuckButton {
    func didPress() {
        print("quack!")
    }
}

class DogButton {
    func didPress() {
        print("bark!")
    }
}

class PigButton {
    func didPress() {
        print("oink!")
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

用户抱怨声音相互重叠。
我们添加一个检查来防止这种情况发生,同时将类相互引入:

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
duckButton.didPress()

let dogButton = DogButton()
dogButton.didPress()

let pigButton = PigButton()
pigButton.didPress()

在您申请成功的浪潮中,政府决定制定一项法律,规定移动设备上的嘎嘎声、吠声和咕噜声只能在工作日的上午 9:00 至 15:00 进行;届时,您的应用程序的用户将面临因使用个人电子设备制作淫秽声音而被判处 5 年监禁的风险。
更改代码:

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    func didPress() {
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    func didPress() {
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

突然,手电筒应用程序开始将我们的应用程序挤出市场,我们不要让它打败我们,通过按“oink-oink”按钮添加手电筒,以及其余按钮:

import Foundation

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

class Flashlight {

    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

class DuckButton {
    var isMakingSound = false
    var dogButton: DogButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard dogButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("quack!")
        isMakingSound = false
    }
}

class DogButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var pigButton: PigButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false &&
                pigButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("bark!")
        isMakingSound = false
    }
}

class PigButton {
    var isMakingSound = false
    var duckButton: DuckButton?
    var dogButton: DogButton?
    var flashlight: Flashlight?
    func didPress() {
        flashlight?.turn(on: true)
        guard duckButton?.isMakingSound ?? false == false && 
                dogButton?.isMakingSound ?? false == false &&
                 Date().mobileDeviceAllowedSoundTime() == true else { return }
        isMakingSound = true
        print("oink!")
        isMakingSound = false
    }
}

let flashlight = Flashlight()
let duckButton = DuckButton()
let dogButton = DogButton()
let pigButton = PigButton()

duckButton.dogButton = dogButton
duckButton.pigButton = pigButton
duckButton.flashlight = flashlight

dogButton.duckButton = duckButton
dogButton.pigButton = pigButton
dogButton.flashlight = flashlight

pigButton.duckButton = duckButton
pigButton.dogButton = dogButton
pigButton.flashlight = flashlight

duckButton.didPress()
dogButton.didPress()
pigButton.didPress()

这样一来,我们就有了一个庞大的应用程序,其中包含大量的复制粘贴代码,里面的类之间通过死链接连接起来——不存在弱耦合,这样的奇迹很难维护,而且由于犯错误的可能性很高,因此未来会发生变化。

使用中介器

让我们添加一个中间中介类 - ApplicationController。该类将提供对象的松耦合,确保类之间的职责分离,并消除重复的代码。
让我们重写一下:

import Foundation

class ApplicationController {

    private var isMakingSound = false
    private let flashlight = Flashlight()
    private var soundButtons: [SoundButton] = []

    func add(soundButton: SoundButton) {
        soundButtons.append(soundButton)
    }
    
    func didPress(soundButton: SoundButton) {
        flashlight.turn(on: true)
        guard Date().mobileDeviceAllowedSoundTime() && 
                isMakingSound == false else { return }
        isMakingSound = true
        soundButton.didPress()
        isMakingSound = false
    }
}

class SoundButton {
    let soundText: String
    
    init(soundText: String) {
        self.soundText = soundText
    }
    
    func didPress() {
        print(soundText)
    }
}

class Flashlight {
    var isOn = false

    func turn(on: Bool) {
        isOn = on
    }
}

extension Date {
    func mobileDeviceAllowedSoundTime() -> Bool {
        let hour = Calendar.current.component(.hour, from: self)
        let weekend = Calendar.current.isDateInWeekend(self)
        
        let result = hour >= 9 && hour <= 14 && weekend == false
        
        return result
    }
}

let applicationController = ApplicationController()
let pigButton = SoundButton(soundText: "oink!")
let dogButton = SoundButton(soundText: "bark!")
let duckButton = SoundButton(soundText: "quack!")

applicationController.add(soundButton: pigButton)
applicationController.add(soundButton: dogButton)
applicationController.add(soundButton: duckButton)

pigButton.didPress()
dogButton.didPress()
duckButton.didPress()

许多有关用户界面应用程序体系结构的文章描述了 MVC 模式及其派生物。模型用于处理业务逻辑数据,视图或表示在界面中向用户显示信息/提供与用户的交互,控制器是确保系统组件交互的中介者。

来源

https://refactoring.guru/ru/design-patterns/调解员

源代码

https://gitlab.com/demensdeum/patterns/< /p>

Strategy pattern

The Strategy pattern allows you to select the type of algorithm that implements a common interface, right while the application is running. This pattern refers to the behavioral design patterns.

Sun Tzu

Suppose we are developing a music player with embedded codecs. The built-in codecs imply reading music formats without using external sources of the operating system (codecs), the player should be able to read tracks of different formats and play them. VLC player has such capabilities, it supports various types of video and audio formats, it runs on popular and not very operating systems.

Imagine what a naive player implementation looks like:

var player: MusicPlayer?

func play(filePath: String) {
    let extension = filePath.pathExtension

    if extension == "mp3" {
        playMp3(filePath)
    }
    else if extension == "ogg" {
        playOgg(filePath)
    }
}

func playMp3(_ filePath: String) {
    player = MpegPlayer()
    player?.playMp3(filePath)
}

func playOgg(_ filePath: String) {
    player = VorbisPlayer()
    player?.playMusic(filePath)
}

Next, we add several formats, which leads to the need to write additional methods. Plus, the player must support plug-in libraries, with new audio formats that will appear later. There is a need to switch the music playback algorithm, the Strategy pattern is used to solve this problem.

Let’s create a common protocol MusicPlayerCodecAlgorithm, write the implementation of the protocol in two classes MpegMusicPlayerCodecAlgorithm and VorbisMusicPlayerCodecAlgorithm, to play mp3 and ogg files with-but. Create a class MusicPlayer, which will contain a reference for the algorithm that needs to be switched, then by the file extension we implement codec type switching:

import Foundation

class MusicPlayer {
    var playerCodecAlgorithm: MusicPlayerCodecAlgorithm?
    
	func play(_ filePath: String) {
            playerCodecAlgorithm?.play(filePath)
	}
}

protocol MusicPlayerCodecAlgorithm {
    func play(_ filePath: String)
}

class MpegMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("mpeg codec - play")
	}
}

class VorbisMusicPlayerCodecAlgorithm: MusicPlayerCodecAlgorithm {
	func play(_ filePath: String) {
		debugPrint("vorbis codec - play")	
	}
}

func play(fileAtPath path: String) {
	guard let url = URL(string: path) else { return }
	let fileExtension = url.pathExtension
		
	let musicPlayer = MusicPlayer()
	var playerCodecAlgorithm: MusicPlayerCodecAlgorithm? 
		
	if fileExtension == "mp3" {
                playerCodecAlgorithm = MpegMusicPlayerCodecAlgorithm()
	}
	else if fileExtension == "ogg" {
                playerCodecAlgorithm = VorbisMusicPlayerCodecAlgorithm()
	}
		
	musicPlayer.playerCodecAlgorithm = playerCodecAlgorithm
	musicPlayer.playerCodecAlgorithm?.play(path)
}

play(fileAtPath: "Djentuggah.mp3")
play(fileAtPath: "Procrastinallica.ogg")

The above example also shows the simplest example of a factory (switching the codec type from the file extension) It is important to note that the Strategy strategy does not create objects, it only describes how to create a common interface for switching the family of algorithms.

Documentation

https://refactoring.guru/en/design-patterns/strategy

Source code

https://gitlab.com/demensdeum/patterns/

Iterator pattern

In this article I will describe the Iterator pattern.
This pattern refers to the behavioral design patterns.

Print it

Suppose we need to print a list of tracks from the album “Procrastinate them all” of the group “Procrastinallica”.
The naive implementation (Swift) looks like this:

for i=0; i < tracks.count; i++ {
    print(tracks[i].title)
}

Suddenly during compilation, it is detected that the class of the tracks object does not give the number of tracks in the count call, and moreover, its elements cannot be accessed by index. Oh…

Filter it

Suppose we are writing an article for the magazine “Wacky Hammer”, we need a list of tracks of the group “Djentuggah” in which bpm exceeds 140 beats per minute. An interesting feature of this group is that its records are stored in a huge collection of underground groups, not sorted by albums, or for any other grounds. Let’s imagine that we work with a language without functionality:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Suddenly, a couple of tracks of the group are found in the collection of digitized tapes, and the editor of the magazine suggests finding tracks in this collection and writing about them. A Data Scientist friend suggests to use the Djentuggah track classification algorithm, so you don’t need to listen to a collection of 200 thousand tapes manually. Try:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

Mistakes

Now, just before sending to print, the editor reports that 140 beats per minute are out of fashion, people are more interested in 160, so the article should be rewritten by adding the necessary tracks.
Apply changes:

var djentuggahFastTracks = [Track]()

for track in undergroundCollectionTracks {
    if track.band.title == "Djentuggah" && track.info.bpm == 160 {
        djentuggahFastTracks.append(track)
    }
}

let tracksClassifier = TracksClassifier()
let bpmClassifier = BPMClassifier()

for track in cassetsTracks {
    if tracksClassifier.classify(track).band.title == "Djentuggah" && bpmClassifier.classify(track).bpm == 140 {
        djentuggahFastTracks.append(track)
    }
}

The most attentive ones noticed an error; the bpm parameter was changed only for the first pass through the list. If there were more passes through the collections, then the chance of a mistake would be higher, that is why the DRY principle should be used. The above example can be developed further, for example, by adding the condition that you need to find several groups with different bpm, by the names of vocalists, guitarists, this will increase the chance of error due to duplication of code.

Behold the Iterator!

In the literature, an iterator is described as a combination of two protocols / interfaces, the first is an iterator interface consisting of two methods – next(), hasNext(), next() returns an object from the collection, and hasNext() reports that there is an object and the list is not over. However in practice, I observed iterators with one method – next(), when the list ended, null was returned from this object. The second is a collection that should have an interface that provides an iterator – the iterator() method, there are variations with the collection interface that returns an iterator in the initial position and in end – the begin() and end() methods are used in C ++ std.
Using the iterator in the example above will remove duplicate code, eliminate the chance of mistake due to duplicate filtering conditions. It will also be easier to work with the collection of tracks on a single interface – if you change the internal structure of the collection, the interface will remain old and the external code will not be affected.
Wow!

let bandFilter = Filter(key: "band", value: "Djentuggah")
let bpmFilter = Filter(key: "bpm", value: 140)
let iterator = tracksCollection.filterableIterator(filters: [bandFilter, bpmFilter])

while let track = iterator.next() {
    print("\(track.band) - \(track.title)")
}

Changes

While the iterator is running, the collection may change, thus causing the iterator’s internal counter to be invalid, and generally breaking such a thing as “next object”. Many frameworks contain a check for changing the state of the collection, and in case of changes they return an error / exception. Some implementations allow you to remove objects from the collection while the iterator is running, by providing the remove() method in the iterator.

Documentation

https://refactoring.guru/en/design-patterns/iterator

Source code

https://gitlab.com/demensdeum/patterns/

模式“快照”

在这篇文章中,我将描述“快照”模式。或“纪念品”

此模式指的是“行为”模式。设计模式。

假设我们正在开发一个图形编辑器,并且我们需要添加根据用户命令回滚操作的功能。同样非常重要的是,在实现此模式时,系统组件无权访问回滚“操作”的内部状态,其他系统组件只能访问快照对象而无法更改;其内部状态,提供清晰、简单的外部接口。为了解决这个问题,使用了“快照”模式。或“守护者”。

工作示例“快照”介绍如下:


单击时,会出现一个精灵,单击卷曲的箭头时,操作会被取消–精灵消失了。该示例由三个类组成:

  1. 显示精灵和图形界面的画布。
  2. 屏幕控制器,它处理点击并控制屏幕的逻辑。
  3. 如有必要,可使用屏幕控制器回滚每次更改后持续存在的画布状态。

在模式“快照”的上下文中课程有:

  1. 画布–源中,此类的状态保存为“快照”,以便以后根据请求回滚。此外,源必须能够在向其传输“快照”时恢复状态。
  2. 控制器保管人,此类知道如何以及何时保存/回滚状态。
  3. 状态–快照,一个存储源状态的类,加上日期信息或可以精确建立回滚顺序的索引。

该模式的一个重要特征是,只有源才能访问快照中已保存状态的内部字段,这是保护快照免受外部更改(来自想要绕过封装进行更改的开发人员的更改)所必需的; ,破坏系统逻辑)。为了实现封装,使用内置类,并且在 C++ 中,它们使用指定友元类的功能。就我个人而言,我为 Rise 实现了一个没有封装的简单版本,并在为 Swift 实现时使用 Generic。在我的版本中– Memento 仅将其内部状态赋予同一类状态的实体:

来源

https://refactoring.guru/design-patterns/memento

源代码

https://gitlab.com/demensdeum/patterns/< /p>

访客模式

在这篇文章中,我将描述一种名为“Visitor”的设计模式。或“访客”
此模式属于行为模式组。

让我们提出一个问题

该模式主要用于绕过早期绑定语言中单次调度的限制。

爱丽丝 X 作者 NFGPhoto (CC-2.0)
让我们创建一个抽象类/协议 Band,创建 MurpleDeep 的子类,创建一个具有两个方法的 Visitor 类 –一个用于将 Band 的任何后代输出到控制台,第二个用于输出任何 MurpleDeep,主要是方法的名称(签名)相同,参数仅因类而异。使用带 Band 参数的中间打印输出方法,我们创建一个 Visitor 实例并调用 MurpleDeep 的访问方法。
下面是 Kotlin 中的代码:

输出将为“这是 Band 类

这怎么可能?!

许多文章(包括俄语)都用巧妙的文字描述了为什么会发生这种情况,但我建议您想象一下编译器如何看待代码,也许一切都会立即变得清晰:

解决问题

有很多解决方案可以解决这个问题,接下来我们将考虑使用访问者模式的解决方案。
我们将带有 Visitor 参数的accept方法添加到抽象类/协议中,在方法内部调用visitor.visit(this),然后将accept方法的重写/实现添加到MurpleDeep类中,果断而冷静地违反了DRY,再次编写Visitor.visit(this).< br />最终代码:

来源

https://refactoring.guru/ru/设计模式/访客双调度

源代码

https://gitlab.com/demensdeum/patterns

蝇量模式

在这篇文章中,我将描述“轻量级”结构模式。或“机会主义者” (蝇量级)
该模式属于结构模式组。

让我们看一下下面该模式如何工作的示例:


为什么需要它? 节省内存。我同意,在Java广泛使用的时代(白白消耗cpu和内存),这不再那么重要,但值得使用。
上面的例子中只输出了 40 个对象,但是如果将数量增加到 120,000,内存消耗也会相应增加。
让我们看看 Chromium 浏览器中不使用享元模式的内存消耗情况:

如果不使用模式,内存消耗约为 300 MB。

现在让我们向应用程序添加一个模式并查看内存消耗:

使用该模式,内存消耗约为 200 MB,因此我们在测试应用程序中节省了 100 MB 内存;在严肃的项目中,差异可能会更大。

它是如何工作的?

在上面的例子中,我们画了 40 只猫,或者为了清楚起见,画了 12 万只猫。每只猫都作为 png 图像加载到内存中,然后在大多数渲染中将其转换为用于渲染的位图(实际上是 bmp),这样做是为了速度,因为压缩的 png 需要很长时间来渲染。不使用该模式时,我们将 12 万张猫的图片加载到 RAM 中并进行绘制,但是当使用“轻量级”模式时,我们会加载 12 万张猫的图片。我们将一只猫加载到内存中,并以不同的位置和透明度将其绘制 12 万次。整个神奇之处在于,我们在渲染时将坐标和透明度与猫图像分开实现,渲染只需要一只猫并使用具有坐标和透明度的对象来正确渲染。

代码是什么样的?

以下是语言 Rise 的示例 >< /p>

不使用模式:


猫图像是为循环中的每个对象单独加载的–猫图像。

使用模式:

一张猫的图片被 12 万个物体使用。

用在什么地方?

用于 GUI 框架,例如 Apple 的“重用”; (重用)UITableViewCell 表格单元格,这为不了解此模式的初学者增加了入门障碍。也常用于游戏开发。

源代码

https://gitlab.com/demensdeum/patterns/< /p>

来源

https://refactoring.guru/ru/design-patterns/蝇量级
http://gameprogrammingpatterns.com/flyweight.html

好的、坏的和丑陋的单身人士

在这篇笔记中,我将描述我和我的同事在处理各种(成功的和不那么成功的)项目时使用单例模式(外国文学中的单例)的经验。我将描述为什么我个人认为这种模式不能在任何地方使用,我还将描述团队中的哪些心理因素影响这种反模式的集成。献给所有跌倒和残废的开发人员,他们试图理解为什么这一切都始于一名团队成员带来了一只可爱的小狗,易于处理,不需要特殊的照顾和知识来照顾它,并以饲养的野兽结束劫持你的项目,需要越来越多的工时,消耗用户的神经和金钱,并为评估看似简单的实施创建绝对可怕的数字事情。


Wolf in sheep’s clothing by SarahRichterArt

故事发生在另一个宇宙,所有巧合都是随机的…

通过 Cat@Home 在家养一只猫

每个人在生活中有时都会有不可抗拒的想要抚摸猫的欲望。世界各地的分析师预测,第一家创建猫配送和租赁应用程序的初创公司将变得非常受欢迎,并且在不久的将来将被 Moogle 以数万亿美元收购。很快这种事就发生了。秋明州的一个人创建了 Cat@Home 应用程序,很快就成为了万亿富翁,Moogle 公司获得了新的利润来源,数百万压力重重的人们有机会请一只猫到他们家进一步熨烫并平静下来。

克隆人的攻击

来自摩尔曼斯克的一位极其富有的牙医 Alexey Goloborodko 对《福布斯》上一篇有关 Cat@Home 的文章印象深刻,决定他也想成为天文数字般的富有。为了实现这个目标,他通过朋友找到了一家来自 Goldfield &#8211 的公司。提供软件开发服务的 Wakeboard DevPops 向他们订购了 Cat@Home 克隆版本的开发。

获胜团队

该项目名为Fur&Pure,委托给一支由20人组成的才华横溢的开发团队;接下来我们关注一个5人的移动开发团队。每个团队成员都分担自己的工作,以敏捷和 Scrum 武装,团队按时(六个月内)完成开发,没有错误,在 iStore 中发布应用程序,在 iStore 中被 100,000 个用户评为 5,有很多关于应用程序有多棒、服务有多出色(毕竟是另类宇宙)的评论。猫熨好了,应用程序发布了,一切似乎都很顺利。不过,Moogle 并不急于斥资数万亿美元收购一家初创公司,因为 Cat@Home 中不仅出现了猫,还出现了狗。

狗叫,商队继续前进

应用程序的所有者决定是时候将狗添加到应用程序中,要求公司进行评估,并收到大约至少六个月的时间来将狗添加到应用程序中。事实上,应用程序将再次从头开始编写。在此期间,Moogle 将在应用程序中添加蛇、蜘蛛和豚鼠,而 Fur&Pur 将只接收狗。
为什么会发生这种情况?缺乏灵活的应用程序架构是一切的罪魁祸首;最常见的因素之一是单例设计反模式。

出了什么问题?

为了在家订购一只猫,消费者需要创建一个请求并将其发送到办公室,办公室将处理该请求并将猫寄给快递员,快递员将已经收到服务费用。
一位程序员决定创建一个类“Cat Application”;具有必要的字段,通过单例将此类带入全局应用程序空间。他为什么要这么做?为了节省时间(节省半小时的一分钱),因为公开应用程序比考虑应用程序架构和使用依赖项注入更容易。然后其他开发人员选择这个全局对象并将他们的类绑定到它。
例如,所有屏幕本身都访问全局对象“CatRequest”;并显示应用程序上的数据。这样,这样一个单体应用程序就被测试并发布了。
一切似乎都很好,但突然出现一位客户,要求将狗的请求添加到应用程序中。团队开始疯狂地评估系统中有多少组件将受到此更改的影响。分析结束时,我们发现有必要重做 60% 到 90% 的代码,以便让应用程序在全局单例对象中不仅接受“对 Cat 的请求”,还可以接受“请求猫”。而且还是“申请一只狗”,现阶段评估其他动物的添加已经没什么用了,应付至少两只。

如何防止单例

首先,在需求收集阶段,明确指出创建灵活、可扩展架构的需求。其次,值得对产品代码进行独立审查,并对薄弱环节进行强制研究。如果你是一名开发人员并且喜欢单身,那么我建议你在为时已晚之前醒悟过来,否则肯定会失眠和神经紧张。如果您正在开发一个具有大量单例的遗留项目,请尝试尽快摆脱​​它们或项目。
您需要从单例全局对象/变量的反模式切换到依赖注入–最简单的设计模式,在初始化阶段将所有必要的数据赋予类的实例,而无需进一步绑定到全局空间。

来源

https://stackoverflow。 com/questions/137975/what-is-so-bad-about-singleton
http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
https://blog.ndepend.com/singleton-pattern-costs/

死亡面具开发报告 1

新的非永久部分“开发者日记”;或者外国风格的开发日记。
游戏Death-Mask的开发正在紧锣密鼓地进行中,引擎标志已为 2019 Flame Steel Engine 游戏添加了用于按岛屿(绿色、红色、黑色、白色)选择初始地图的屏幕、迷宫墙壁、天花板、地板的纹理输出、增加了游戏区域的尺寸。


红区城市地图

接下来,我们计划添加环境的 3D 模型,而不是《毁灭战士》风格的精灵,并且我们计划添加武器、盒子、敌人和朋友的模型。在游戏中,计划添加货币、商店、购买游戏地图部分的能力(指示有战利品的有趣地点)以及“死亡面具”的可能位置。我还想添加雇佣同伴穿越网络迷宫的功能。
关注新闻。

斯威夫特 4.2.3 –乌班图18.10

使用必要的库构建 Swift 以在 Ubuntu 18.10 上运行。 Apple 网站上的最新可用版本 –适用于 Ubuntu 18.04。基于官方网站的汇编,添加了 Ubuntu 18.04 的库。还添加了一个示例脚本,用于为 bash 终端添加 PATH 和 LD_LIBRARY_PATH:
http://www.mediafire.com/file/lrs74mvoj3fti03/swift4.2.3.ubuntu.18.10.x86_64.tar.gz/file

声明性语言扎卡兹

我向您展示一种纯声明式编程语言——扎卡兹。新语言的主要思想–该应用程序包含以自由形式编写的执行命令,必须由“执行器”执行。如果没有“表演者”无法执行命令,程序执行停止。应用程序称为技术规范 (tez),并且必须具有 .tez 扩展名。 Zakaz 语法需要两个规则:

  • 每个命令都从新行开始
  • 每个命令都必须以人类可以理解的正式语言编写

Hello World.tez 示例:

在屏幕上显示“Hello World”文本在屏幕上显示“Zakaz 'tez' example”文本

工作说明书示例,显示操作原理的描述以及在 Firefox 浏览器中打开网站 http://demensdeum.com

在屏幕上显示“显示网站演示”文本显示 "您需要在系统上安装 Firefox 才能运行此 'tez',并且应该可以通过 \"系统\"C 函数" 屏幕上的文本显示"还应该有\"FirefoxPerformer\"分配给 Zakaz Runtime,请查看手册了解更多信息”屏幕上的文字在 Firefox 中显示地址为 "http://demensdeum.com" 的网站

您必须与“执行器”一起运行上面的示例; FirefoxPerformer,能够处理通过 Firefox 渲染站点的最新命令

./ZakazRuntime openDemensdeumSite.tez FirefoxPerformer

要实现您的执行器,您需要使用抽象类 ZakazRuntime::Performer 将其实现为动态库,并从 createPerformer() 全局函数方法将其与智能指针一起返回。您可以使用 FirefoxPerformer 实现作为示例。

源代码

https://gitlab.com/demensdeum/zakaz