软件框架的目的是为了解耦_共同生活:解耦代码和框架

软件框架的目的是为了解耦

Of course you develop using the latest technologies and frameworks. You’ve written 2.5 frameworks yourself, your code is PSR-2 compliant, fully unit-tested, has an accompanying PHPMD and PHPCS config, and may even ship with proper documentation (really, that exists!). When a new version of your favorite framework is released, you’ve already used it in your own toy project and submitted a couple of bug reports, maybe even accompanied with a unit test to prove the bug and a patch that fixes it. If that describes you, or at least the developer you want to be: reconsider the relationship your code has with the framework.

当然,您将使用最新的技术和框架进行开发。 您已经自己编写了2.5个框架 ,您的代码符合PSR-2要求 ,并且经过了完整的单元测试,具有随附的PHPMDPHPCS配置,甚至可能附带了适当的文档(确实存在!)。 当您喜欢的框架的新版本发布时,您已经在自己的玩具项目中使用了该框架,并提交了一些错误报告,甚至可能伴随有单元测试以证明该错误以及修复该错误的补丁。 如果这描述了您,或者至少是您想要成为的开发人员,请:重新考虑您的代码与框架之间的关系。

框架与你 (The Framework and You )

Most code written today at a professional level is dependent upon some framework in one way or another. This is a good thing as it means developers are aware they’re not alone in the world and are reusing the work of others to save loads of time in the long run. There’s plenty of arguments to be found online on why you should use frameworks, and in this article I’m taking it as a proven best practice. But exactly how dependent is your code on the framework?

今天,大多数以专业水平编写的代码都以某种方式依赖某种框架。 这是一件好事,因为这意味着开发人员意识到他们并不孤单,从长远来看,他们正在重用他人的工作以节省时间。 在线上有很多关于为什么要使用框架的争论,在本文中,我将其视为一种行之有效的最佳实践。 但是,您的代码到底对框架有多依赖?

In my off-hours I like to hang out in the IRC channel #zftalk on irc.freenode.net and help others. When Zend Framework 2 (ZF2) was in the works, a notable trend in the channel was people asking when it would be released. Not because they were eager to use it, but because they didn’t want to start a new ZF1 project when ZF2 was about to hit. A decent project could easily take up to 3 months and if they had to start over by the end of the development process to be able to ship code that depends on “the latest and greatest” then developing it now for ZF1 would be a huge waste of time. The thought is totally understandable. Nobody likes to put time, effort and/or money into something only to find out it’s outdated and has lost half its value. If you spend 3 months coding, you want it to be the best thing released to date with no apparent flaws.

在下班时间,我喜欢在irc.freenode.netIRC频道#zftalk闲逛 ,并帮助其他人。 当Zend Framework 2(ZF2)投入使用时,该渠道中的一个显着趋势是人们询问何时发布。 不是因为他们渴望使用它,而是因为他们不想在ZF2即将推出时开始一个新的ZF1项目。 一个像样的项目可能很容易需要3个月的时间,如果他们必须在开发过程结束之前重新开始才能发布依赖于“最新和最大”的代码,那么现在为ZF1开发它将会是一个巨大的浪费。时间。 这个想法是完全可以理解的。 没有人喜欢将时间,精力和/或金钱投入到某件事情上,只是发现它已经过时并失去了一半的价值。 如果您花3个月的时间进行编码,则希望它是迄今为止发布的最好的东西,并且没有明显的缺陷。

So, use Symfony (or any other framework) instead? A lot of people went this route, or even completely switched languages (Python and Ruby being popular), so they wouldn’t have to delay their projects. Others completely put their projects off, pushing them back until after the ZF2 release date! Delaying a project should never be an option, so that leaves switching frameworks to not have to suffer the version bump. But let me tell you this right now: you should develop with ZF1, even if ZF2 could hit tomorrow. Rephrase that with ZF2 and ZF3 if you want to, or insert your favorite framework and the current and future version.

因此,改用Symfony(或任何其他框架)? 很多人都走这条路,甚至完全切换了语言(Python和Ruby很流行),因此他们不必拖延他们的项目。 其他人则完全推迟了他们的项目,将它们推迟到ZF2发布日期之后! 不应延迟项目,以免切换框架不必遭受版本冲突。 但是,现在让我告诉您这一点:即使ZF2明天会面,您也应该使用ZF1进行开发。 如果需要,可以使用ZF2和ZF3来重新表述,或者插入您喜欢的框架以及当前和将来的版本。

城市孤独 (Urban Solitude )

For the sake of argument, let’s pretend it’s 2011 and work on ZF2 is in progress but there’s no defined timeline yet; it’s going to be done when it’s done.

为了争辩起见,我们假设它是2011年,正在进行ZF2的工作,但尚无明确的时间表。 完成后将要完成。

While it is awesome that you re-use as much code as your favorite framework has to offer, your code has to be able to switch frameworks within a matter of days. Are you are a master of ZF1? Then write your new project in ZF1, even though ZF2 might hit next month. If you design it right, that will not be a setback even if the project stakeholders decide the project has to ship with ZF2 support. Depending on the amount of framework components you use, this change can easily be done within a week. And with the same amount of effort, you can completely switch framework vendors, and use Symfony, CakePHP, Yii, or whichever framework instead. If you write your code without coupling dependencies, and instead write small wrappers that interface with the framework, your real logic is shielded from the harsh outside world where frameworks might be upgraded or replaced. Your code lives happily in it’s own little world where everything it’s dependent on stays the same.

尽管您重用了自己喜欢的框架所提供的代码真是太棒了,但是您的代码必须能够在几天之内切换框架。 您是ZF1的大师吗? 然后即使ZF2可能在下个月发布,也要在ZF1中编写新项目。 如果设计正确,那么即使项目涉众决定该项目必须附带ZF2支持,也不会有任何挫折。 根据您使用的框架组件的数量,可以在一周内轻松完成此更改。 只需付出相同的努力,您就可以完全切换框架供​​应商,并使用Symfony,CakePHP,Yii或其他任何框架。 如果您在编写代码时没有耦合依赖关系,而是编写了与框架接口的小型包装程序,那么您的实际逻辑就不会受到可能会升级或替换框架的恶劣外部环境的影响。 您的代码快乐地生活在自己的小世界中,在这个小世界中,所依赖的一切都保持不变。

This all sounds very nice in theory, but I understand it can be difficult to wrap your head around without having some code examples . So, we’re still in 2011, still waiting for ZF2, and we have this awesome idea for a component that will answer the ultimate question of life, the universe, and everything. Given that it will take a little bit of time to compute the answer, we decide to store the result so that if the question if ever asked again then we can fetch it from the datastore instead of waiting another 7.5 million years to recalculate it. I’d love to show the code that actually computes the answer, but since I don’t know the ultimate question either, I’ll instead focus on the data storage part.

从理论上讲,这一切听起来都很不错,但是我知道,如果没有一些代码示例,可能很难把头缠起来。 因此,我们仍然在2011年,仍在等待ZF2,我们对组件的想法很棒,它将回答生命,宇宙和一切的终极问题。 鉴于计算答案需要一点时间,我们决定存储结果,以便如果再次提出问题,则可以从数据存储区中获取它,而不必等待另外750万年重新计算它。 我很想展示实际计算答案的代码,但是由于我也不知道最终的问题,因此我将重点放在数据存储部分。

<?php
$solver = new MyUltimateQuestionSolver();
$answer = $solver->compute();
// now that we have the answer, let's cache it
$db = Zend_Db::Factory('PDO_MYSQL', $config['db']);
$record = array('answer' => $answer);
$db->insert('cache', $record);

Plain, simple, works as designed. But this will break when we swap out ZF1 for ZF2, Symfony, etc.

简单,简单,按设计工作。 但是,当我们将ZF1换成ZF2,Symfony等时,这将中断。

Notice that we used the decoupled vendor mechanism of Zend_Db. This same code will work just fine for another data storage if we just swap PDO_MYSQL for another wrapper. The insert() and factory() calls will still work, even if we switch to, say, SQLite. So why not do the same thing for the framework itself?

注意,我们使用了Zend_Db的解耦供应商机制。 如果我们只是将PDO_MYSQL交换给另一个包装器,则相同的代码也可以很好地用于另一个数据存储。 即使我们切换到SQLite, insert()factory()调用仍将起作用。 那么,为什么不对框架本身做同样的事情呢?

Let’s move the code into a small wrapper:

让我们将代码移到一个小的包装器中:

<?php
class MyWrapperDb
{
    protected $db;

    public function __construct() {
        $this->db = Zend_Db::Factory('PDO_MYSQL', $config['db']);
    }

    public function insert($table, $data) {
        $this->db->insert($table, $data);
    }
}

// Business Logic
$solver = new MyUltimateQuestionSolver();
$answer = $solver->compute();
// now that we have the answer, let's cache it
$db = new MyWrapperDb();
$db->insert('cache', array('answer' => $answer));

We’ve taken the framework-specific details out of the business logic and can now swap frameworks at any time by only modifying the wrapper.

我们已经从业务逻辑中删除了特定于框架的细节,现在仅通过修改包装器就可以随时交换框架。

Staying in 2011, now let’s say our stakeholders decide we need to release with MongoDB support because it’s the hottest buzzword right now. ZF1 doesn’t support MongoDB natively, so we drop the framework here and use the PHP extension instead:

停留在2011年,现在让我们的利益相关者决定我们需要在MongoDB支持下发布,因为它是当前最热门的流行语。 ZF1本机不支持MongoDB,因此我们在此处删除框架,改用PHP扩展:

<?php
class MyWrapperDb
{
    protected $db;

    public function __construct() {
        $mongo = new Mongo($config['mongoDSN']);
        $this->db = $mongo->{$config['mongoDB']};
    }

    public function insert($table, $data) {
        $this->db->{$table}->insert($data);
    }
}

// Business Logic
$solver = new MyUltimateQuestionSolver();
$answer = $solver->compute();
// now that we have the answer, let's cache it
$db = new MyWrapperDb();
$db->insert('cache', array('answer' => $answer));

精致的抽象 (Abstraction Refined )

If you paid attention, you’ll notice that none of the business logic has changed when we switched to MongoDB. That’s exactly the point I’m trying to make: by writing your business logic decoupled from the framework (be it ZF1 in the first example or MongoDB in the second example), your business logic stays the same. It doesn’t take much imagination to see how you can adapt the wrappers to every possible framework for data storage out there without having to change anything in the business logic. So, whenever ZF2 drops, your code stays exactly the same. You don’t have to go through each and every line of your application to see if it uses anything from ZF1 and then refactor it to use ZF2; all you have to update is your wrappers and you’re done.

如果您注意的话,您会发现当我们切换到MongoDB时,任何业务逻辑都没有改变。 这正是我要提出的要点:通过编写与框架分离的业务逻辑(第一个示例中为ZF1或第二个示例中为MongoDB),您的业务逻辑将保持不变。 无需花费太多想象力即可了解如何在无需更改业务逻辑中任何内容的情况下,使包装器适应每个可能的框架来存储数据。 因此,每当ZF2下降时,您的代码就保持不变。 您不必遍历应用程序的每一行来查看它是否使用ZF1中的任何内容,然后将其重构为使用ZF2。 您只需要更新包装器就可以了。

If you use this together with Dependency Injection/Service Locator or a similar design pattern, you can very easily swap wrappers around. You make one interface, a design contract that all wrappers of that type must adhere to, per solution and the wrappers can be swapped around at will. You can even write a simple mockup wrapper adhering to the same interface and unit testing will be a breeze.

如果将其与“ 依赖项注入/服务定位器”或类似的设计模式一起使用,则可以非常轻松地交换包装器。 根据解决方案,您只需创建一个接口,即该类型的所有包装器都必须遵守的设计合同,并且可以随意交换这些包装器。 您甚至可以编写一个遵循相同接口的简单模型包装器,而单元测试将变得轻而易举。

Let’s add an interface and a mockup wrapper, and since ZF2 has already been released, let’s add a wrapper for that too:

让我们添加一个接口和一个模型包装器,由于ZF2已经发布,因此我们也为此添加一个包装器:

<?php
Interface MyWrapperDb
{
    public function insert($table, $data);
}

class MyWrapperDbMongo implements MyWrapperDb
{
    protected $db;

    public function __construct() {
        $mongo = new Mongo($config['mongoDSN']);
        $this->db = $mongo->{$config['mongoDB']};
    }

    public function insert($table, $data) {
        $this->db->{$table}->insert($data);
    }
}

class MyWrapperDbZf1 implements MyWrapperDb
{
    protected $db;

    public function __construct() {
        $this->db = Zend_Db::Factory('PDO_MYSQL', $config['db']);
    }

    public function insert($table, $data) {
        $this->db->insert($table, $data);
    }
}

class MyWrapperDbZf2 implements MyWrapperDb
{
    protected $db;

    public function __construct() {
        $this->db = new ZendDbAdapterAdapter($config['db']);
    }

    public function insert($table, $data) {
        $sql = new ZendDbSqlSql($this->db);
        $insert = $sql->insert();
        $insert->into($table);
        $insert->columns(array_keys($data));
        $insert->values(array_values($data));
        $this->db->query(
            $sql->getSqlStringForSqlObject($insert),
            $this->db::QUERY_MODE_EXECUTE);
    }
}

class MyWrapperDbTest implements MyWrapperDb
{
    public function __construct() { }

    public function insert($table, $data) {
        return ($table === 'cache' && $data['answer'] == 42);
    }
}

// -- snip --

public function compute(MyWrapperDb $db) {
    // Business Logic
    $solver = new MyUltimateQuestionSolver();
    $answer = $solver->compute();
    // now that we have the answer, let's cache it
    $db->insert('cache', array('answer' => $answer));
}

Using the interface at the dependency injection point has imposed a rule on the wrappers: they must adhere to the interface or the code will raise an error. That means they must implement the insert() method, else they won’t satisfy the contract. Our business logic can rely on that method being present by type-hinting the interface, and really doesn’t have to care about the implementation details. Whether it’s ZF1 or ZF2 storing the data for us, the MongoDB extension, a WebDAV module uploading it to a remote server: the business logic doesn’t care. And as you see in the last example, we can even write a small mockup wrapper, implementing the same interface. If we make the Dependency Injection/Service Locator use the mockup during unit testing then we can reliably test the business logic without needing any form of data storage present. All we really need is the interface.

在依赖项注入点使用接口已对包装器施加了规则:包装器必须遵守该接口,否则代码将引发错误。 这意味着他们必须实现insert()方法,否则将无法满足合同要求。 我们的业务逻辑可以通过类型暗示接口来依赖存在的该方法,而实际上不必关心实现细节。 无论是ZF1还是ZF2为我们存储数据,MongoDB扩展,一个WebDAV模块将其上传到远程服务器:业务逻辑都不在乎。 正如您在上一个示例中看到的那样,我们甚至可以编写一个小的模型包装器,实现相同的接口。 如果我们使依赖性注入/服务定位器在单元测试期间使用模型,那么我们就可以可靠地测试业务逻辑,而无需使用任何形式的数据存储。 我们真正需要的只是接口。

结论 (Conclusion )

Even though your code probably isn’t so complex that it takes 7.5 million years to run, you still should design it to be portable in case the earth does get destroyed by Vogons and you have to redeploy it on a different planet (or framework). You cannot assume your favorite framework will stay backwards compatible forever or will even be around forever. Frameworks, even backed by big companies, are an implementation detail and should be decoupled as such. That way, your cool genius application can always support the latest and greatest. The real logic will live happily in the little bubble created by wrappers, shielded from all the evil implementation details and angry dependencies. So when ZF3/ Symfony3/whichever-else-big-thing gets announced: don’t stop writing code, don’t learn new frameworks because you have to (you should because you want to learn more, though), be productive inside the bubble and write the wrappers for the next big thing as soon as the next big thing gets released.

即使您的代码可能并不复杂,以至于需要花费750万年的时间才能运行,但您仍然应该将其设计为可移植的,以防地球被Vogons破坏,并且您必须将其重新部署在其他星球(或框架)上。 您不能假设自己喜欢的框架将永远保持向后兼容,甚至永远存在。 甚至由大公司支持的框架都是实现细节,因此应该分开。 这样,您出色的天才应用程序就可以始终支持最新和最强大的应用程序。 真正的逻辑将快乐地存在于包装器创建的小气泡中,不受所有有害的实现细节和愤怒的依赖的影响。 因此,当ZF3 / Symfony3 / whatever-else-big-thing宣布时:不要停止编写代码,不要学习新的框架,因为必须(尽管应该,因为您想了解更多),在冒泡并在下一个重要事件发布后立即为下一个重要事件编写包装。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/living-apart-together-decoupling-code-and-framework/

软件框架的目的是为了解耦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值