`
bufanliu
  • 浏览: 200640 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

一篇比较好演示AS的重构方法

    博客分类:
  • FLEX
阅读更多
[翻譯]重構讓世界更美好(Making the world better via refactoring - Intro)
這些文章翻譯自Sho Kuwamoto的ActionScript Refactoring 三部曲,經過原作者同意翻譯之。
本文原文連結在此

我非常喜歡 Ely的一個作品:DragTile 元件( demo )。 為了做出不同的效果,我想延伸它原有的功能行為,並且使其更具彈性。我第一個想到的就是:Refactoring( 譯註: 中文通常翻譯為:”重構”,為重新建構之意,筆者在此保留原文,免除翻譯的差異 )。 如果你不常或輩子從沒做過refactoring,那麼,且聽我一步步道來,很有趣的。


何謂Refactoring?
Refactoring :在不改變原有外部功能的前提下,以漸進地手法改寫程式碼的結構。”不改變原有功能“聽起來很怪,卻是精隨所在。
分成兩部份來看: coding and refactoring,coding階段時,我們加上了新的功能(functionality);refactoring階段,我們重新調整程式碼結構,同時確保功能運行依舊。請牢記”不改變原有功能”的前提,這會讓你在進行調整程式碼時,不會迷失方向,當新的程式碼運作功能與舊的一樣,就是一次成功的refactoring。

有Refactoring有彈性
通常來說,refactoring 要讓現有的程式碼更有彈性,有些時候,你要refactoring的目標很明顯;有些情形下,你必須在refactoring前好好地思考規劃該如何改寫程式碼。
在Ely的例子裡,我們想要讓畫面的排版(layout)方式更容易被改變,看看DragTile的原始碼,有些部份不論排版如何,都是一樣;有些部份隨著排版而變動,因為排版的演算法(algorithm)在不同案例下會有不同變化,因此我們可以把它抽離出來,獨立成另一個類別(Class)。

Tip 1:

Cleanly separate out the code that you think will need to change often into a separate class.
技巧一: 把你認為會常常變動的程式碼分離出來

我們用繼承的手法,將DragTile裡關於排版的程式碼分開,建立成如下的關係:


(圖1 繼承)
FlexibleContainer, 負責一般的行為,如: 過場動畫、滑鼠互動等。
DragTile, 負責特定的排版方式,運算物件的位置。
把這些工作分離開後,我們可以更容易地建立新的類別以達成新的排版方式(比如排成一個圓之類的),而其他程式碼:item renderer的溝通、動畫等等還可繼續沿用。

另一個手法:合成(Composition)

(圖2 合成)
FlexibleContainer是一個容器,它包括了許多子元件,子元件一般行為的程式碼都會在這裡。
TileLayout管理排版的helper Class, 任何排版相關的計算都是它的責任。

如圖所示,Container類別將排版的任務委派(Delegate)給TileLayout來處理,本手法有一些好處:

以”委派”的觀念實作,通常可以使物件行為得以動態改變。承上,我們可以抽換其中一個Container的排版而不需重新調整繼承關係(reparenting)。
將大型類別拆解成數個小類別的合成(非繼承),將有助於調整、擴充程式結構,如同各個擊破一般。相對繼承手法來看,如果我們把FlexibleContainer再分離成兩個類別:一個是輕量化的Container專門為下載效率設計;另一個處理快取(cache)、本地化(localization),那麼,DragTile該繼承哪一個類別呢? 這將是一個難題,然而,如果你採用了委派的作法,將排版的工作委派給Layout類別,你不會有這個困擾。
合成手法,通常可以讓系統更分工(decoupling)更具彈性。舉例來說,你正在做照片管理的模組,如果你用了最上面說的繼承手法,你很難建立一個專門排版的模組,相對地,用合成手法,排版的功能是可以依使用者需要而直接改變的。
概觀上述所言,有技巧如下:

Tip 2: Think hard before using inheritance. Composition is almost always a better way to separate out the flexible part of a class from the invariant part.
技巧二: 你真的要用繼承嗎? 請三思。合成往往是比較好的選擇。

現在我們有refactoring的基礎概念了,在下一篇我們將深入探討程式碼。

[翻譯]ActionScript重構三部曲之一(Advanced ActionScript Refactoring - Step 1)
這些文章翻譯自Sho Kuwamoto的ActionScript Refactoring 三部曲,經過原作者同意翻譯之。
本文原文連結在此

上一篇,我們探討了基本的知識,以及如何利用refactoring讓DragTile更彈性,如果妳還沒有看過,可以去瞥幾眼再回來,
回來了嗎? 那我們來看看一些程式碼吧。

Step 0 - 開始
原本的程式碼檔案請到Ely的部落格下載,或者你可以下載我的稍微修改版。

Step 1 - 抽離出FlexibleContainer類別
雖然我上一篇說過要用合成不用繼承,但是我還是先用繼承一下,這樣會比較容易建立出helper class。

Tip 3: Always refactor in small steps that leave the external behavior unchanged
切記以小部份地進行refactor,並且確保物件的行為不變

先建立一個父類別(superclass),開始看DragTile的原始碼,一個個方法(譯註:method通常譯為”方法”, 可是我喜歡翻成”函式”  )一個個屬性(property)地看,把合適的函式和屬性放到superclass裡頭。
我把那些看起來很一般的區域變數歸類到superclass裡頭, _items 陣列, renderers 陣列也是一樣,其他看起來專門為了排版的變數保留在DragTile裡。
因為大部分變數都是private,被移動到superclass之後,會造成很多編譯錯誤的訊息,我通常也會把相關函式的移入superclass。
在一個情況下(dragTargetIndex)我需要建立一個protected變數讓子類別可以取得父類別的資料,然而,這是正確的途徑嗎? 在這個例子裡,可能不是,”正確”的方式,應該是在drag操作時,把資料以參數的方式傳遞出去。 這提示了我們:

Tip 4: When refactoring, don’t try to make it “perfect”. Just strive to incrementally improve the code each time you touch it.
Refactoring時,不要想一次就達到完美,只要一次比一次好一點就可以了

建立一個protected變數是分開兩個class最快的方法,我們等會再來修改。
原本的api:


第一次refactoring的目標是將有關於排版的邏輯程式都移到一個類別裡,其他的類別盡亮都放在superclass裡,DragTile類別 應該越小越好,調整過一次的api:


第一次Refactor的原始碼


[翻譯]ActionScript重構三部曲之二(Advanced ActionScript Refactoring - Step 2)
這些文章翻譯自Sho Kuwamoto的ActionScript Refactoring 三部曲,經過原作者同意翻譯之。
本文原文連結在此。

在前一步創造了一個父系類別,由他來掌管排版的邏輯演算。現在我們換個做法,用『合成』的概念來做做看:我希望這個部分的Refactoring把DragTile的功能轉移到另一個Helper Class(譯註: Helper Class顧名思義為協助型類別,通常定義了一些大家會共用到的運算式、常數等),最後DragTile只約化成一個空殼。
現在看來,我們不用再擔心Container和Layout Manager之間要如何拉關係了,回頭來看一下第一步,我們決定類別工作的過程有點隨便...。
那麼,到底要移動哪些方法、屬性到Helper Class呢?

我們得先問問自己,主要的物件(Container)將如何與Helper Class溝通,先來看看DragTile的定義:


注意那些標示成綠點的屬性,看起來都是特定給DragTile用的,同時,最底下有三個方法看起來很"普通": measure(), findItemAt(), and generateLayout(),我們把這三支抽離出來寫成一個interface(譯註: 此處指 OOP 的interface, 並非 User Interface)專門針對排版運算。
由於IUIComponent 已經定義了measure()這個方法,沒有必要令ILayout再定義一次,反之,我決定新增一個方法叫做 getMeasuredSize() 來呼叫measure()。針對排版運算管理,我還新增了兩個方法: attach() 及 detach(),如此一來,LayoutManager 可透過這些方法將物件加上或清除,最後, ILayout長像這樣:


接下來,FlexibleContainer可以加上layout屬性了,並建立一個TileLayout類別來實作ILayout定義的方法。 我們如何由第一步演進至第二步的呢? 第一步,新的副類別FlexibleContainer出現了;第二步,不會再有DrageTile類別了。
依照Refactoring的慢慢、小部份修改的原則,可是很難做到,因此我導出另一個技巧:

Tip 5: If needed, build temporary scaffolding to make sure your code continues to “work” as you refactor.
Refactor時,可建立一個暫時的類別,先把很多很多工作委任給它,讓原有功能保持正常運作

本例中,Refactor到一半時,我同時運用了繼承和委派。換句話說,即使我已經規劃了TileLayout類別,我仍舊多寫了一些程式碼保留DragTile。



如果你不熟析refactor背後的奧妙,你會懷疑:這"暫時類別"是什麼鳥?! 然而只要你習慣了,你會愛上它的。往往一次大改比小改容易成功許多,暫時類別是讓你無後顧之憂地繼續一步步refactoring。
下階段,移出 DragTile的方法功能,我不會把整堆方法都移出,反之,我會運用以下原則把這些方法放進對應類別:

如果程式碼是"一般的"邏輯操作,交給FlexibleContainer
如果與Tile排版有關,放到TileLayout
這些原則大都沒問題,惟獨遇到Style(譯註: Flex Framework的CSS架構),由於TileLayout並非UIComponent,因此沒有內建的CSS操作,DragTile有定義一些CSS,如vGap, hGap,我們依然可以讓TileLayout具有Style的功能的,然而這必須大費功夫,所以我決定了,暫時關閉CSS style的運作:

Tip 6: When temporarily disabling functionality during a big refactor, be sure to do so in a way that preserves information, ideally through stub functions.
當進行Refactor而關閉了某些功能,必須安排個方法保存對應的資料

把CSS關掉的最快方法:註解掉程式碼,但很難確定這些程式碼如果移到他處,是否仍舊運作無誤? 有鑑於此,我建立一個getStyle()方法,只回傳NaN(譯註: No a Number) 如此這般,Ely的CSS依舊正確地存取數值,但不使用罷了,這個作法可以避開大部分因移動程式碼造成的編譯器錯誤。

最後我把DragTile所有的程式碼分離至兩個類別裡頭,DragTile最後只剩下:

PLAIN TEXTActionscript:
public class DragTile extends FlexibleContainer
{
    public function DragTile()
    {
        super();

        layout = new TileLayout();
    }
}

顯然地,我們現在可以不管DragTile了。
二部曲的程式碼

翻譯]ActionScript重構三部曲之三(Advanced ActionScript Refactoring - Step 3)
這些文章翻譯自Sho Kuwamoto的ActionScript Refactoring 三部曲,經過原作者同意翻譯之。
本文原文連結在此。

最後一關了,快破關了。
一開始的版本會變成這個模樣,排版方式可以動態改變了!
回想第二步,我們沒做什麼refactoring,只有把Styles關掉,然後移出DragTile的程式碼,再把Styles加回去,之後Layout都擁有自己的Style了。

為了確保排版可以動態改變,我新寫了一個排版類別: CircleLayout ,再整理一次CircleLayout和TileLayout,解析兩者共通的部份,抽離出另一個父類別: Layout ,現在類別圖如下:


我還"修正"了一些地方,比如Drag/Drop(譯註: 拖拉-放)一直令我有點疑惑,因此我用了一個神祕的方法改寫掉了(看你能不能找到!)
最後測試看看refactor之後,排版方式是否那麼容易地抽換,原本的DragTile有600多行程式碼,而新的CircleLayout只有100多行,裡面只做了相關的物理運算,沒有其他管理renderer 與 animator的行為。

PLAIN TEXTActionscript:
public class CircleLayout extends Layout
{
// ILayout interface
override public function getMeasuredSize():Point
{
    return getMaxSize();
}

override public function findItemAt(px:Number, py:Number, seamAligned:Boolean):Number
{
    // Can't execute this if we aren't attached to a container.
    if (!container || container.renderers.length == 0)
        return NaN;

    // Get the radius and center of the circle.
    var radius : Number = Math.min(unscaledContainerWidth, unscaledContainerHeight) / 2;
    var hCenter : Number = unscaledContainerWidth / 2;
    var vCenter : Number = unscaledContainerHeight / 2;

    var angle : Number = Math.atan2(py-vCenter, px-hCenter);
    if (angle <0)
        angle += 2 * Math.PI;

    // figure out the closest "item" by working backwards from the angle to the index, using floating point math.
    var result : Number = container.renderers.length * angle / (2 * Math.PI);

    // depending on whether this is seam aligned, do a ceil or round.
    result = (seamAligned) ? Math.ceil(result) : Math.round(result);

    // do a modulo op to make sure that this is within [0, length-1]. Modulo is the correct
    // operator in this case because this is a circle.
    result %= container.renderers.length;
    return result;
}

override public function generateLayout():void
{
    // Get the radius and center of the circle.
    var radius : Number = Math.min(unscaledContainerWidth, unscaledContainerHeight) / 2;
    var hCenter : Number = unscaledContainerWidth / 2;
    var vCenter : Number = unscaledContainerHeight / 2;

    // Find the max item size.
    var maxSize : Point = getMaxSize();
    var max : Number = Math.max(maxSize.x, maxSize.y);

    // Inset the radius by the max size.
    radius -= max;

    // Loop through the items and position them.
    var length : int = container.renderers.length;
    for (var idx:int = 0; idx <length; idx++)
    {
        var renderer:IUIComponent = container.renderers[idx];
        var target:LayoutTarget = animator.targetFor(renderer);//targets[idx];

        // evenly space each item over 2*pi radians.
        var angle : Number = (2 * Math.PI) * idx / length;

        // position items on a circle.
        target.scaleX = target.scaleY = 1;
        target.item = renderer;
        target.unscaledWidth = renderer.getExplicitOrMeasuredWidth();
        target.unscaledHeight = renderer.getExplicitOrMeasuredHeight();
        target.x = hCenter + radius * Math.cos(angle) - target.unscaledWidth/2;
        target.y = vCenter + radius * Math.sin(angle) - target.unscaledHeight/2;
        target.animate = true;
    }

    // If there is more than one item, and if there is a drag target, nudge the items next to the drag target
    if (length> 1 && container.dragTargetIndex>= 0 && container.dragTargetIndex <length)
    {
        // Find the items to the left and right of the target.
        var leftIndex : int = (container.dragTargetIndex + length - 1) % length;
        var rightIndex : int = (leftIndex + 1) % length;

        var leftTarget : LayoutTarget = animator.targetFor(container.renderers[leftIndex]);
        var rightTarget : LayoutTarget = animator.targetFor(container.renderers[rightIndex]);

        // exaggerate the difference between the two targets by a factor of maxSize/2.
        var dx : Number = rightTarget.x - leftTarget.x;
        var dy : Number = rightTarget.y - leftTarget.y;
        var distance : Number = Math.sqrt( dx*dx + dy*dy );

        leftTarget.x -= dx / distance * max/2;
        leftTarget.y -= dy / distance * max/2;
        rightTarget.x += dx / distance * max/2;
        rightTarget.y += dy / distance * max/2;
    }
}

protected function getMaxSize() : Point
{
    // Can't execute this if we aren't attached to a container.
    if (!container)
        return new Point(0, 0);

    // Find the max item size.
    var maxWidth : Number = 0;
    var maxHeight : Number = 0;

    if(container.renderers.length> 0)
    {
        for(var i:int=0;i<container.renderers.length;i++)
        {
            var itemRenderer:IUIComponent = container.renderers[i];
            maxWidth = Math.ceil(Math.max(maxWidth,itemRenderer.getExplicitOrMeasuredWidth()));
            maxHeight = Math.ceil(Math.max(maxHeight,itemRenderer.getExplicitOrMeasuredHeight()));
        }
    }

    return new Point(maxWidth, maxHeight);
}

可以再改良嗎? 當然可以。layout與container仍舊留有一些連結,可能可以移除掉的,我先做到這裡就好,找機會再繼續Refactor吧。

Refactor最終版






分享到:
评论
1 楼 matt.u 2009-05-16  
好像有点深奥。

相关推荐

    基于卷积神经网络的页岩重构方法.pdf

    该方法结合页岩图像软数据,进行了页岩重构,实验证明该方法只需要少量的真实页岩数据即可获得较好的重构结果。 卷积神经网络(Convolutional Neural Network,CNN)是一种深度学习算法,近年来在图像处理和计算机...

    压缩感知重构方法

    在标题“压缩感知重构方法”中,重点在于如何利用这种理论来高效重建一维信号。 在压缩感知中,关键在于信号的稀疏性。如果一个信号在某种基或变换域中可以被表示为少数非零系数,那么我们称这个信号是稀疏的。例如...

    基于GPU的快速夏克-哈特曼波前重构方法研究.pdf

    1. 夏克-哈特曼波前重构方法:一种用于波前重构的方法,通过Zernike多项式系数和波前数据的矩阵求解来实现波前重构。 2. 基于GPU的快速夏克-哈特曼波前重构方法:一种基于GPU的快速算法,通过并行化矩阵求解和数据...

    测量噪声条件下基于扩展变量和最佳平方逼近的重构方法的研究

    【测量噪声条件下基于扩展变量和最佳平方逼近的重构方法的研究】是关于复杂网络重构技术的一篇研究论文,由翁平、李汉鹏、史润东和王世红等人撰写。该研究关注的重点在于如何在存在测量噪声的情况下,利用扩展变量和...

    一种基于STM32的星载FPGA在轨重构方法研究及地面验证.pdf

    综上所述,这篇论文探讨了一种基于STM32微控制器的星载FPGA在轨重构技术,并通过地面验证实验来证明了该重构方法的可行性和效率。这项技术对于提高卫星系统的灵活性、可靠性和维护性具有重要意义,并且对于推动卫星...

    OMP压缩感知重构方法

    ompdemo.m可能是用于演示OMP算法的示例程序,它可能创建一个稀疏信号,对其进行压缩采样,然后利用OMP进行重构。ompspeedtest.m可能用于测试不同参数下的运行速度,以评估算法效率。Contents.m可能提供了代码的整体...

    逆向工程曲面重构方法比较

    逆向工程曲面重构方法的比较研究涵盖了基于经典点——线——面流程的传统式曲面造型和基于网格化处理的快速式曲面造型。研究的目的是为了探讨这两种造型方式在逆向工程曲面重构过程中的差异以及如何有效地结合使用这...

    基于LSTM神经网络的多源数据融合桥梁变形重构方法.pdf

    本文介绍了一种基于长短时记忆(LSTM)神经网络的多源数据融合方法,用于桥梁变形重构。该方法可以融合应变和加速度数据,实现高精度的桥梁变形重构。 LSTM神经网络 LSTM(Long Short-Term Memory)是一种特殊类型...

    基于MATLAB的汽车路面不平度重构方法研究.pdf

    本篇研究文献《基于MATLAB的汽车路面不平度重构方法研究》聚焦于利用MATLAB软件工具,探讨了汽车路面不平度重构的各种算法,并对这些算法进行了比较研究。路面不平度是指路面表面的高低不平状态,对于汽车的行驶性能...

    基于人工神经网络的WENO重构方法.pdf

    本文提出了一种基于人工神经网络的WENO重构方法,用于解决流体力学中的某些初边界值问题(IBVP)。在这种方法中,人工神经网络(ANN)被用于近似WENO-JS中的非线性权重,其中ANN的输入为用于五阶WENO求解的模板中的五...

    xiaobobianhuan.rar_二维小波分解_小波 演示_重构算法

    在这个"xiaobobianhuan.rar"压缩包中,包含了一个关于二维小波分解和重构算法的演示。这个演示可能包括了以下步骤: 1. **数据预处理**:在进行小波分解之前,通常需要对原始矩阵数据进行预处理,如去除噪声、归一...

    重构重构重构

    《重构:改善既有代码的设计》是一本由Martin Fowler所著的经典IT著作,它在软件开发领域具有极高的影响力。重构是提升代码质量、可读性和维护性的重要手段,旨在不改变代码外在行为的前提下,改进其内部结构。在这...

    CS多种稀疏重构方法以及仿真结果matlab

    在给定的标题“CS多种稀疏重构方法以及仿真结果matlab”中,我们可以理解这是关于使用MATLAB实现的多种压缩感知(Compressive Sensing, CS)稀疏重构算法的集合,可能包括了理论分析、代码实现和实验结果。...

    FPGA软件重构验证方法研究.pdf

    新提出的验证方案是一种自顶向下的层次化验证测试方法,这种方案结合了FPGA软件重构的特点和当前主流软件重构实现形式。通过这种层次化的测试,可以更有效地针对可重构FPGA软件进行验证测试。与传统验证测试手段相比...

    一种基于改进型游程编码的FPGA动态重构方法.pdf

    根据提供的文档信息,本篇知识点聚焦于FPGA(现场可编程门阵列)的动态重构技术,特别是基于改进型游程编码(Run-Length Coding)的一种新方法。以下是对文档内容所涉及知识点的详细阐述: 1. FPGA动态重构的定义和...

    27丨理论一:什么情况下要重构?到底重构什么?又该如何重构?1

    重构是软件开发过程中的一个重要环节,它涉及到代码的优化、设计改进和质量提升,而不改变程序的外部行为。本文将详细探讨重构的目的、对象...理解重构的目的、对象、时机和方法,是成为一名优秀软件工程师的重要步骤。

Global site tag (gtag.js) - Google Analytics