plazma - amatör bilgisayar kültürü

"Usta... ActionScript 3.0 üzerine pişmişinden OOP kes, az da MVC döküver..."

Cem (Spaztica) Gencer

Beşiktaş'ta sabit pazarın yanında, üst geçidin altında yer alan ve yakın zamanda kapanan Bolu Et Lokantasını düşünürken attığım bu başlık, aslında tüm yazının içeriğini anlatıyor -ya da benim acıkmaya başladığımı... FutureWave Software (1993) olarak başlayan, kısa sürede (1996) MacroMedia tarafından satın alınan sonra da -hala yabancılığını çeksek de- Adobe'un gücünden beslenmeye başlayan bu platform, ağır gelişim evresini aşarak yapısının giderek kurallara bağlı hale gelmesi ve OOP gibi çağdaş bir şekle girmesiyle sağlıklı hale geldi.

Uzun süredir Flash'a göz ucuyla bakıp çeşitli denemelerinizde performansı düşük olduğu için burun kıvırıyorduk. AS2 ve öncesinde kullanıcılar için düşünülmüş esneklikler, hantallaşmaya sebep oluyordu. Kurallara ve OOP ilkelerine daha bağlı hale gelen ActionScript 3 ile artık daha erişkin bir dil / platformdan söz etmek mümkün. Yeni duyulmaya başlayan Flash 10 (Astro) dedikodularından da Flash'ın konumlanmasının radikal olarak değişeceği; C++ erişimine, 3D rotasyona ve IK (bones) ile daha pratik animasyonlar oluşturulabileceğini geçenlerde bir blogda okudum. Yani Adobe, Flash ekibinin oluşturduğu ivmeyi, sağladığı kaynaklarla da daha da artırdı. Önümüzdeki versiyonlarda bizi neyin beklediğini kestirebilmek giderek zorlaşacak. Flex ve Air gibi yan teknolojilerle, Spry ve yakında alınacağını duyduğum AMFPHP gibi destekleyici framework'lerle Flash'ın gücü gerçekten katlanacağa benziyor. Karşı cephede MacroFos'un oluşturmaya çalıştığı SilverLight gibi bir ürünün zamanın getirdiği bu deneyimle baş etmesi ve sağlıklı bir yer edinmesi zor olacağa benzer. Kurumsal müşteriler, elbette her zaman uyuşturucu bağımlısı gibi kendi torbacılarından beslenmek isteyeceklerdir. Neticede bir torbacı da olsa, müşterisini kaybetmek istemez.

Bu yazıda OOP ilkelerinin ve MVC'nin AS3 üzerinde nasıl uygulanabildiğini göstermeye çalışacağım. AS2'den AS3'e geçmek isteyenler de buradaki temel prensiplere sadık kalırlarsa sorunları azalacaktır. Bu temeller olmadan önümüzdeki sayılarda ele alacağım konuları kavramak zor olurdu. Görsel efekt ve benzeri demo kültürünü ilgilendiren konular ağırlıklı ilgi alanımız olacak; basit bannerlar yapmak ya da dinamik içerik sunmak gibi konularda web üzerinde yazılmış tonla kaynak zaten mevcut.

Yazılarım, terminoloji açısından klasik bilişim çevirilerinden biraz farklı olabilir. Object Oriented Programming tanımlamasını herkesin kabul ettiği gibi nesne yönelimli diye çeviremiyorum; bunun önemli sebebi pratiklik. İngilizce terminoloji halinden fazla uzaklaşmazsak, başka bir yerde object olarak okuduğunuz bir tanımlamanın nesne olup olmadığını düşünmezsiniz. Bunun gibi bazı terimleri ingilizce bırakmak okunurluğu rahatlatan, uygulama açısından da pratik olacak bir tercih.

1. Kontrol Etmek, Kontrol Edilmektir...

Bir Zen ustası kontrolü reddeder. Kontrol etmeye çalıştığınız sürece kontrol sizi kendisine bağlı kılar. Kodunuzu tek bir yerde toplamak, kontrolü elinizde tutmak gibidir. O güç asla sizde olmayacaktır, asla kontrolü elinizde tutamazsınız, tuttuğunuzu düşündüğünüzde aslında siz kontrol altında tutuluyor olursunuz. Kontrol de asla ulaşılamayan gereksiz bir amaç olur. Budistlerin Samsara olarak adlandırdığı ve farkındalıkla kurtulmaya çalıştıkları sonsuz döngü ancak farkındalık ve dışında durmayı tercih etmekle kırılabilir; aynı şekilde kontrol döngüsü de.

Düz akışlı ve fonksiyon bazlı kodlamada alıştığımız herşeyi bir dosyada, bir listede toplamak, aşırı kontrolün sonucu; yazdığımız programa ait her parçanın elimizin altında olmasını isteriz. OOP'un oluşmasıyla, her oluşturulan objeyi bir yaşam biçim olarak görebiliriz. Bu objelere verdiğiniz görevlere, yapacaklarına güvenmeniz, işleri bu şekilde bölüştürüp delege etmeniz, vaktinizi artıracak, karışıklığı ise azaltacak bir yol.

Yazdığınız kod parçalarını amaçlarına göre düzenleyin ve her bir metodu, gerçek amaçları için kullanarak kendinizi özgür bırakın. Amaçları iç içe geçmiş kod yığınları sadece kodunuzu karıştırmaya ve sizi bug'larla uğraşmaya mahkum bırakır.

2. Hantallık ya da Aşırı Esneklik...

ActionScript'in önemli bir yavaşlatıcı unsuru, esnek model idi. Bunun sayesinde programlama deneyimi fazla olmayan kişiler, rahatlıkla 3-4 satır ekleyerek pratik fonksiyonları gerçekleştirebiliyordu. En basitinden değişkenlerin tipinin tanımlanmamış olması, player'ın her türlü değişkeni kendi içinde inceleyerek uygun şekilde ele alması gibi bir yavaşlatıcı etkene sebep oluyordu. Bu yüzden AS3 kullanırken değişken tiplerini tanımlamak bir gereklilik oldu. Daha önceden kolaylık sağlayan Number tipi de int ve uint gibi iki gruba ayrıldı. Bu sayede int ile poiztif ve negatif değerleri, uint ile de sadece pozitif değerleri tanımlıyoruz. uint'in avantajı, negatif değerleri göstermek için bir bit harcamadığından daha büyük numaraları (int'in 2 katı) tanımlamakta kullanılabiliyor oluşu. Number'da olduğu gibi player bu değişkenin içerik tipini algılamaya çalışmadığından daha hızlı işlem yapabilecektir.

3. Erişim

Şimdiye kadar az-çok Flash ile uğraşan herkes aşağıdakine benzer bir kodu MovieClip'e kod ekleyerek bununla timeline'ın akışına müdahale etmişlerdir.

_parent.gotoAndStop(2);

Bunu AS3 ile çalıştırmaya kalktığınızda compiler tonla hata kusacaktır. Sebebi, AS3'ün artık class inheritance şeklinde çalışmasıdır. AS2 prototype inheritance temelli olduğu için bu gibi pratik müdahaleler kolaylıkla yapılabiliyordu. AS3'e geçerken bunu kurallara uygun hale getirmek için

var myParent = parent;
myParent.gotoAndStop(2);

şeklinde yazmamız, problemi çözecektir. Burada myParent ile yeni bir obje tanımlayarak erişimimizi onun üzerinden yapıyoruz. Elde etmek istediğimizi bu şekilde çok az değiştirerek OOP kurallarına tam bir uygunluk sağladığımızda AS3'e geçişte problemlerimiz de azalacaktır. Aynı yöntemle String ve fonksiyonlarda da değişken tipini önden tanımlamamız, daha hızlı ve hatasız çalışmamızı sağlayacaktır.

var numOut:Number = 15;
var numOut:int = 15;
--------------------------------
var slowVar = "myString";
var fastVar:String = "myString";
--------------------------------
function slowFunc()
{
    return 100;
}
function fastFunc():Number
{
    return 100;
}

4. Class

AS3'de görsel arabirim dahil herşey class üzerinden tanımlanmıştır. Flash'a ait temel class'lar, compile aşamasında otomatik olarak movie içine dahil edilir.

Class'ları oluştururken package ile oluşturacağımız metodları gruplayabiliriz:

package
{
    public class Circle
    {
    ...
    }
}

Temel class'lar dışında bir class kullandığınızda onu compiler'a tanıtmazsanız kullanamazsınız. Kullandığınız class'ları kullandığınız yerde import etmeniz -temel class'lardan olsalar da- temel bir alışkanlık olmalı. Bunun için import komutunu kullanıyoruz:

package
{
    import flash.display.MovieClip;
    public class Circle
    {
    ...
    }
}

Timeline üzerinde class import ederken birden fazla yerde aynı class'ı kullanacaksanız ya her kullanıldığı yerde import etmeniz ya da aşağıdaki gibi class'ı oluştururken erişim yolunu da tanımlamanız gerekiyor:

var myFoo:foo = new macr.util.foo();

5. Public / Private

Günün birinde bir programcı, dünyadaki tüm edebiyatı, görsel sanatları, sayıları ve istatistikleri bir araya getiren sonsuz bir veritabanı oluşturdu. Sonra buna her türlü sorgulamayı kolaylıkla yapabileceği bir sorgulama sistemi tasarladı ve yazdı. Ondan sonra da çalışmasının meyvelerini tadabilmek için bilgisayarının karşısına oturdu.

3 dakika sonra başı ağrımaya başladı. 3 saat sonra rahatsızlık duymaya ve kötüleşmeye başladı. 3 gün sonra, tüm veritabanını imha etti. Sebebini sorduklarında "Bu sistem tüm verileri önüme getiriyor, herşeyi incelememi ve sorgulamamı sağlıyordu. Her türlü bilgiye erişebiliyor, her şeyi görebiliyordum, her yeri gezebiliyordum. Bu, bilinebilecek her şeyi öğrenmemem için bahanelerimi ortadan kaldırıyor. Yemek yiyemez, uyku uyuyamaz hale geldim. Tek yapabildiğim, bu sonsuz veritabanında gezinmekti. Artık dinlenebilirim."

Sonsuz açıklık ve erişilebilirlik, sağlıklı değildir. Kodunuza ait her şeyin açık ve erişilebilir olması, müdahalelerin yolunu açar. Sizin planladığınızın dışında müdahaleler de kodunuzun sağlıksız çalışmasına yol açar.

Public ile bir metodu ya da property'yi dışarıdan erişime açık tutmak istediğimizi belirtebiliriz. Bunun tersi Private özellikle class iç hesaplamalarında kullanılabilecek metod / property'ler içindir.

6. Property

AS3 öncesi property dediğimiz objelere ait değerlere erişim için

myClip_mc._alpha = 50;

şeklinde önünde _ işaretli bir yaklaşım uyguluyorduk. AS3 ile hem bu öndeki _ işareti kalkıyor, hem de 0-100 arası değerler artık 0...1 aralığında olacak şekilde veriliyor. AS2'ye kadar yüzde şeklinde cinsinden değer belirtmek, player'ın ilgili property değeri üzerinde çarpma ve sonra 100'e bölme işlemi yapmasını, dolayısıyla da hızın azalmasına sebep oluyordu.

7. Event

AS3 öncesi bir movieclip'i mouse ile etkileşimini sağlamak çok pratik idi; ilgili movieclip'in koduna şu türden bir kod eklemekle Flash'ın Button tanımlaması da tarih olmaya başlamıştı:

on (release){
    doSomething();
}

AS2 ile bu yöntem, timeline'da yazılabilecek şu şekilde bir kodla da sağlanabiliyordu:

myButton.onRelease = function(){
    doSomething();
}

Ama artık AS3 ile herşey kurallara çok uygun olmak durumunda. Objeler arası etkileşim, bunların birbirini dinlemesi ve buna uygun tepki göstermesiyle olabilir. Bu yüzden listener (dinleyici) oluşturup istediğimiz durum gerçekleştiğinde bir işleyiş oluşturabiliriz.

function ClickHandlerFunction(event)
{
    doSomething();
}
myButton.addEventListener(
    MouseEvent.CLICK, ClickHandlerFunction);

Aslında yaptığımız, klasik bir fonksiyon tanımlamasında parametre olarak bir event objesi getirecek şekilde dönüştürerek bunu bir dinleyiciye bağlamak. MouseEvent.CLICK, dinlenecek durum tipini tanımlanmasını sağlıyor. Fonksiyonda kullanmayacak olsanız bile parametre olarak event objesini belirtmeniz gerekiyor, yoksa durum gerçekleştiğinde parametresiz tanımlanmış fonksiyona parametre geçmeye çalıştığınız için hata verecektir. event objesini incelediğinizde bunun, durumu gerçekleştiren objeye refere eden bir obje olduğunu görürsünüz. Farklı durumları tek bir fonksiyon ile algılamak istediğinizde bu event objesini kontrol ederek öğrenebilirsiniz.

8. Inheritance

Gerçek bir OOP, class'ların birbirinden türetilebildiği bir kalıtım sistemini de barındırır. Kendisini oluşturan class'a ait metod ve property'lerin devralınmasına inheritance (kalıtım) denmektedir. Bu yaklaşım, var olan çeşitli class'ları kendi metodlarımızla geliştirmemizi sağlıyor. Önceden prototype ifadesi ile Array, Math, MovieClip gibi çok kullanılan class'lara ek fonksiyonlar tanımlıyorduk. AS3 artık prototype yaklaşımı yerine class inheritance kullandığından prototype genişletme yönteminin pabucu dama atıldı.

public class Circle extends MovieClip
{
...
}

şeklinde bir class tanımlaması, MovieClip class'ını genişleterek ona Circle isimli bir metod ekleyecektir. Aşağıdaki örnekte Circle metodu, MovieClip class'ına yeni bir fonksiyon olan daire çizmeyi ekliyor. radius ve fillColor, dışarıdan erişilebilen property'ler ve fonksiyon, bunları parametre olarak alarak daireyi MovieClip class'ının drawCircle() metodu ile çiziyor.

package
{
    import flash.display.MovieClip;
    public class Circle extends MovieClip
    {
        public var radius = 10;
        public var fillColor = 0xFF0000;
        public function drawTheCircle()
        {
            this.graphics.clear();
            this.graphics.beginFill(fillColor);
            this.graphics.drawCircle(0 , 0, radius);
        }
    }
}

Timeline'da kullanılacağı zaman

import Circle;
var myCircle = new Circle();
myCircle.radius = 20;
myCircle.fillColor = 0x0000FF;
myCircle.drawTheCircle();

yazmak yeterli olacaktır.

9. Static

Class'ın her yeni kullanımında değişmeyecek değerleri ve metodları Static kelimesi ile tanımlayabiliriz. Bu şekilde statik yapılan öğe, class'dan oluşturulan her çocuk objede ayrı ayrı tutulmaz, tek değer/metod tüm o class'ın çocukları için geçerli olur. Bir obje yaratıldığında, class'da Static olmayan tüm property ve metotlar, her çocuk objeye kopyalanır. Ama Static tanımlanan property ve metotlar, sadece class'a ait olurlar, onun çocuklarına aktarılmazlar.

Statik ögeleri, bir class'dan kaç defa türetildiğini kontrol etmek için kullanabilirsiniz. Buna örnek olarak bir slideshow indeksini verebilirim. İleri ve geriye gidildiğinde kaçıncı resimde olduğumuz ve başa / sona ulaşıp ulaşmadığımızı, bu değerleri statik yaparak kontrol edebiliriz.

class as_scripts.SlideShow3 {
    public function SlideShow3 ()
    {
    }
    public static function lMovie(s_movie:String)
    {
        var m_empty:MovieClip = 
            _root.createEmptyMovieClip(
            "m_empty", 100);
        m_empty._x = 100;
        m_empty._y = 100;
        m_empty.loadMovie(s_movie);
    }
}

Bu class üzerinden türetilen her objede IMovie metodu var gibi gözüküyor. Ama normal şekilde

import as_scripts.*;
var a:SlideShow3 = new SlideShow3();
a.lMovie("../images/pic_1.jpg");

şeklinde bir kullanımda "error message: Static members can only be accessed directly through classes." gibi bir hatayla karşılaşırsınız.

SlideShow3 class'ının IMovie metoduna erişmek için.

import as_scripts.*;
SlideShow4.lMovie("../images/pic_1.jpg");

şeklinde doğrudan o class'a erişmeniz gerekiyor. Aynı şekilde bir property'yi de Static tanımlamanız gerektiğinde

class as_scripts.SlideShow3right {
    public static var s_movie:String =
       "../images/pic_1.jpg";
    public function SlideShow4right ()
    {
    }
    public static function lMovie () {
        var s_mymovie:String = s_movie;
        var m_empty:MovieClip = 
            _root.createEmptyMovieClip(
            "m_empty", 100);
        m_empty._x = 100;
        m_empty._y = 100;
        m_empty.loadMovie(s_mymovie);
    }
}

ile değişkeni static olarak tanımlamalısınız. Aksi halde statik olan metodumuz, statik olmayan (ve her kopyada yer alan) değişkene erişmeye çalıştığında "error message: Instance variables cannot be accessed in static functions." şeklinde bir hata verecektir.

10. Get / Set

OOP'un güzel yanı, oluşturduğumuz objelerin kapalı birer kutu gibi davranabilmesi ve yazan kişinin izin verdiği ölçüde dışarıya bilgi vermesidir. Class'ın içinde çok karışık hesaplamalar yapabiliriz. İçerde onlarca, hatta yüzlerce değişken kullanabiliriz, ama bu her değişkeni, her an erişime sunabileceğimiz demek değildir. Daha önceden kullandığımız getProperty ve setProperty metodları class'ımızda çalışacaktır. Ama OOP'un temelinde bir değişkene erişmek istiyorsak, ona erişen bir getter ya da onu değiştiren bir setter fonksiyon yazmak daha düzenli ve kontrollü olmamızı sağlar. Getter, değer döndüreceği için return kullanmamız kaçınılmaz. Aynı zamanda metoda :Number tanımlamasıyla bir rakam döndüreceğini belirtebiliriz. :Void ile setter metodunun herhangi bir geri dönüşü olmayacağını belirtiriz.

class as_scripts.Adjust_xy {
    private var nu_xDistance:Number;
    private var nu_yDistance:Number;
    private var mo_fclip:MovieClip;
    private var m_clip:MovieClip;
    public function Adjust_xy (mo_fclip)
    {
        m_clip = mo_fclip;
    }
    function get Fix():Number
    {
        return m_clip._x;
    }
    function set Fix(nu_xDistance):Void
    {
        m_clip._x = nu_xDistance;
    }
    function get Fiy():Number
    {
        return m_clip._y;
    }
    function set Fiy(nu_yDistance):Void {
        m_clip._y = nu_yDistance;
    }
}

11. MVC

Zen, daha üst seviyede algılamakla ilgilidir. Bir bayrak dalgalandığında herkes onun dalgalandığını görür. Bir Zen öğrencisi ise bayrağı dalgalandıran rüzgarı algılar. Daha ileri seviyedeki bir Zen öğrencisi ise ne bayrak, ne de rüzgarı dikkate alır, onu algılayışımızın düşünme biçimimizle ilgili olduğunu görür. Hayat içerisindeki acı ve sevinçlerimizin düşünme biçimimizin yarattığı prangalarımız olduğu Budist düşünüş biçiminin temellerini oluşturur.

MVC'yi programlamanın Zen'i olarak görebiliriz. Karışık yapıları yüzünden bize acı çektiren bir çok hata prangasından kurtulmak da daha üst bir bakış açısıyla olabilir. Zen'i kavramanın bir kitap okumakla olamayacağı gibi MVC'nin de kemikleşmesi bol pratik yapmakla ve bu yaklaşımı sürekli hale getirmekle ilgili. Aslında Zen gibi, MVC de çok basit ve herkesin kolaylıkla kullanabileceği bir yapıdır. http://en.wikipedia.org/wiki/Design_pattern_(computer_science) başlığında benzer çeşitli yazılım şablonlarını bulabilirsiniz.

MVC'nin açılımı Model-View-Controller'dır. Gösterim, veri ve denetleme mekanizmalarını birbirinden ayrı tutarak daha odaklı halde kod yazmamızı sağlayan bu yaklaşım, daha organize çalışmamızı sağlar. Model, veritabanı, veri alışverişi gibi bilginin kaydedildiği ve okunduğu program kısımlarını içerir. View, içeriğin görselleştirildiği, kullanıcıyla iletişimin kurulduğu, front-end olarak düşünebildiğimiz bölümlerdir. Controller ise bu ikisi arasında durarak hangi durumda hangi veriyi getirileceğini, hangi gösterim şablonlarının kullanılacağını belirleyen mekanizmadır. Verinin geçireceği dönüşümler de genellikle Controller içerisinde tanımlanır. MVC, özellikle ölçeklenebilir yapılarda işimizi rahatlatan yapısal bir tasarımdır. Daha modüler bir yapı, özellikle iş bölümü yapılmasında ve herkesin bir bölüme odaklanmasında rahatlık sağlar. Kodu ayrıştırdığı için kısmi müdahaleler ya da yapının başka bir zaman değiştirilmesi, radikal bir çalışma olmaz. Yapıyı yeni bir veritabanına adapte etmek, farklı bir görsel arabirim hazırlamak ya da işlevsel kısımları geliştirmek çok daha pratik gerçekleşir.

Aşağıdaki basit örnek, dört dosyadan oluşuyor. Dosya düzeni de aşağıdaki gibi olmalı. mvctest.fla'yı compile edip çalıştırdığınızda controller, yeni bir model yaratacak ve ardından mvctest.fla üzerinden view'ın out() fonksiyonu çalıştırılacak. Görüldüğü gibi çok basit halde verinin Model ile, ekrana çıkışın View ile, genel kontrolün de Controller ile yapılmasını sağlıyoruz. Bunu geliştirip istediğimiz uygulamaları veri alışveriş ve ekran şablonu sistemine oturtup Event sistemini de devreye alarak çeşitli durumlara göre kendi kontrollerini idare eden bir yapıya dönüştürebiliriz.

bu örneğin dosyalarını http://www.obsesif.net/files/mvc.rar adresinden çekebilirsiniz

mvc/
    com/
        mvctest/
            Controller.as
            Model.as
            View.as
mvctest.fla

/* ======================================= */
/* dosya ismi: mvctest.fla                 */
/* ======================================= */
import com.mvctest.*;
var mc = new Controller();
mc.out();

/* ======================================= */
/* dosya ismi: com/mvctest/Controller.as   */
/* ======================================= */
package com.mvctest {
    import com.mvctest.*;
    public class Controller {
        private var dataset;
        public function Controller ( ) {
            this.dataset = new Model();
        }
        public function out ( ) {
            var viewset = new View( 
                this.dataset.getCoords( ) );
        }
    }
}
/* ======================================= */
/* dosya ismi: com/mvctest/Model.as        */
/* ======================================= */

package com.mvctest {
    public class Model {
        private var xPos:Number;
        private var yPos:Number;
        public function Model ( ) {
            this.xPos = 100;
            this.yPos = 100;
        }
        public function getCoords ( ):String {
            return "xPos: " + this.yPos 
                + ", yPos: " + this.yPos;
        }
    }
}
/* ======================================= */
/* dosya ismi: com/mvctest/View.as         */
/* ======================================= */
package com.mvctest {
    public class View {
        public function View( content:String ){
            trace( content );
        }
    }
}
plazma - 2008