مقدمهای بر Docker
دو مفهوم اساسی در محیط Docker وجود دارند که دانستن آنها ضروری است: Image و Container
image عملا چیزی است که از آن برای Build یک Container استفاده میشود. image دارای یک سری فایلهای لازم و اساسی است که باعث میشود بر روی یک Operation System اجرا شود؛ مثل Ubuntu یا Windows. بنابراین شما Application Framework خود را خواهید داشت و همچنین Databaseی که با آن کار میکند. بنابراین قابلیت استفاده از زبانها و فریم ورکهای مختلف چون Asp.net Core, Nodejs, Python و غیره را خواهد داشت. یک image به خودی خود غیر قابل استفاده است تا زمانیکه بر روی یک Container توزیع شده باشد، تا قابلیت اجرا پیدا کند. بنابراین نقطهی شروع اصلی اجرایی یک برنامه با Container مربوط به آن میباشد.
به صورت خلاصه Image یک template از نوع Readonly است که ترکیبی از لایههای File System میباشد، به همراه فایلهای share شدهی دیگر (از قبیل فریم ورکها و ...) که میتوانند یک Docker Container Instance را تولید نمایند.
Container یک محیط امن و ایزوله است که به وسیلهی image ساخته شده است و میتواند اجرا، متوقف، منتقل و یا حذف شود (بطور قابل ملاحظهای اجرا کردن و متوقف کردن آن سریع میباشد).
تفاوت Docker Containers و Virtual Machines
Virtual Machines همیشه بر روی Host Operation System اجرا میشوند (که میتواند بر روی ویندوز یا لینوکس باشد) و بعد از آن اجرای Guest OS بر روی سطحی به نام Hypervisor. پس میتوان گفت یک کپی کامل از سیستم عامل است که که بر روی hypervisor اجرا میشود و خودش نیز بر روی سخت افزار اجرا میشود. بنابراین میتوان مثل شکل زیر، یک App داشت که عملا یک سری باینری و کتابخانه است و اگر قرار باشد بر روی سیستم عاملهای مختلفی کار کند، احتیاج به کپی کردن کل آن میباشد و بطور واضحی زمان و هزینهی بیشتری برای بالا آوردن آن لازم است.
اما بر خلاف آن، داکر با استفاده از ابزاری به نام Docker Engine کار میکند که میتواند Containerهای مختلفی از OSهای مختلف را اجرا نماید و نیازی به کپی گرفتن از کل سیستم عامل برای اجرای هر container نخواهد بود.
بنابراین با استفاده از ابزارهای مجازی سازی چون Vmware، نسخهی کاملی را از سیستم عامل مطبوع خود میتوان نصب و اجرا نمود؛ اما برخلاف آن با استفاده از داکر، یک نسخهی کوچک از سیستم عامل، بدون وابستگیها و پیچیدگیهای نسخهی اصلی در اختیار خواهد بود.
با این وجود، بوسیله داکر به راحتی میتوان تعداد زیادی از Containerها را به راحتی و با سرعت بالا اجرا نموده و مورد تست و ارزیابی قرار داد.
چطور Docker میتواند سریعتر از Virtual Machineها عمل کند ؟
داکر از چیزی به نام Copy On Write استفاده میکند؛ به معنای کپی کردن همزمان با نوشتن. همانطور که گفته شد هر Container از یک Image ساخته میشود و عملا Imageها همان FileSystemهای از قبل تولید شده هستند و هر کدام از لایهای از کتابخانهها استفاده میکنند که برای اجرای برنامههای کاربردی مورد استفاده قرار میگیرند. سرور آپاچی را در نظر بگیرید، به عنوان یک فایل image که FileSystem بر روی آن ذخیره شدهاست. با نصب Php یک لایه بر روی لایه دیگر ایجاد شده و فقط تغییرات جدید به آن اضافه خواهند شد و حال اگر بخواهید تغییری را بر روی source code خود بدهید، عملا فقط آن تغییر به Image و FileSystem اضافه خواهد شد. این معماری لایه لایه باعث تولید یک FileSystem بصورت read-only میشود که شامل لایههای متفاوتی است و سبب کم حجم شدن آن، بالا رفتن سرعت آن میشود و همچنین با استفاده از Caching، قدرت زیادی را بدان میبخشد.
پس همانطور که در شکل فوق مشاهده میکنید، هر image از لایههای مختلفی تشکیل شده است و توانایی به اشتراک گذاشتن این لایههای متمایز از یکدیگر در Containerها وجود دارد.
بنابراین طبق شکل فوق، بحث را اینگونه خلاصه میکنیم که هر Image از ترکیبی از لایههایی از نوع read-only تشکیل شده است و با اضافه شدن Container، عملا یک لایهی دیگری که قابلیت read/write را دارد بر روی آن اضافه میشود و درون آن source code میتواند قرار گیرد و اینکه بر مبنای شکل زیر میبینید که قابلیت به اشتراک گذاری Image layerها به Containerهای مختلف تعبیه شده است که باعث میشود لایهی نصب شده بر روی سیستم، بصورت اشتراکی قابل استفادهی مجدد باشد و فضای دیسک کمتری، به علاوه سرعت اجرای بالاتری را داشته باشد. هر لایه یک مقدار هش شدهی یکتایی را در اختیار دارد تا از لایههای دیگر تمیز داده شود و قابل شناسایی باشد.
داکر در شبکه چگونه کار میکند؟
ضمنا نکتهی قابل توجه که در مقالههای بعدی به صورت عملی به آن میپردازیم این است که با استفاده از داکر میتوانیم وب سرورهایی را بر روی Containerهای مختلفی داشته باشیم که همگی بر روی پورت بطور مثال 80 هستند؛ طوری که درون هر Container بدلیل ایزوله بودن پروسسهای مخصوص Container مربوط به خود، به پورتهای باز داخل آن شبکه دسترسی دارند و میتوانند پورت در نظر گرفته شدهی درون Container را با پورت دیگری بیرون Container به اصطلاح Expose نمایند.
ضمن اینکه نکتهی دیگری که وجود دارد، ارتباط Containerها با یکدیگر است. برای مثال یک Container برای Database و دیگری برای WebApp میباشد که باید به همدیگر link شده تا قابل استفاده گردند و عملا نیازی به نوشتن ip یکدیگر در این حالت وجود ندارد. البته راههای دیگری از قبیل Compose کردن نیز وجود دارد که در ادامه بیشتر با آنها آشنا خواهیم شد.
Docker Volume چیست؟
بحث دیگری که وجود دارد، Volumeها هستند که قسمتی از FileSystemها میباشند و بصورت ساده، مثال کاربردیاش میتواند قسمتی از یک سیستم و دایرکتوری خاصی را بر روی Container خاصی Map کردن باشد و عملا داخل آن دایرکتوری میتواند source code بوده باشد (یکی از راههای ممکن برای map کردن source code به container) و بر روی Container ایجاد شود.
فوایدی که با استفاده از Volumeها میتوان به آن رسید از قبیل موارد زیر میباشند:
قابلیت به اشتراک گذاری یک Volume بین Containerهای مختلف که به شدت میتواند قابل استفاده باشد.
Data Volumeها ماندگار هستند. یعنی حتی بعد از اینکه Container مربوطه را حذف نمایید، volume مربوط به آن بطور اتوماتیک حذف نمیشود (مگر اینکه خودتان دستور حذف کردن آن را وارد نمایید). پس عملا قابلیت استفادهی مجدد را نیز خواهد داشت.
طبق شکل فوق ما میتوانیم درون یک container یک volume داشته باشیم. وقتی ما چیزی را درون آن مینویسیم عملا داریم در قسمت خاصی به نام Docker Host عمل write کردن را انجام میدهیم که باعث میشود داکر متوجه آن شود. وقتی اسمی را به یک Volume انتساب میدهیم همانند /var/www، در واقع یک اسم مستعار (alias) میباشد که اشاره میکند به این Docker host موجود. در ادامه بیشتر با Volumeها آشنا خواهیم شد.
DockerFile و ساخت imageها چگونه است؟
روش دیگر برای اجرای source code در داکر، ساخت یک image اختصاصی از آن و اجرا کردن آن بر روی یک container مجزا است. با استفاده از DockerFile میتوانید imageهای خود را build کرده که عملا هر image در آخر باید به یک سیستم عامل برسد و همانطور که گفته شد به صورت لایهای کار میکنند و مراتب اجرای آن از قبیل working directory و expose کردن بر روی پورتی خاص، همچنین استفاده از Environment Variableها میباشد و همچنین با استفاده از DockerHub (که نسخهی enterprise نیز دارد) میتوان imageهای ساخته شده را بر روی cloud نگه داشت و همهی اعضای تیم از یک image بخصوص استفاده کنند؛ برای مثال همهی اعضای تیم از یک نسخهی Nodejs استفاده کنند و اشتباها بر روی ماشینهای توسعهی مختلف برنامه نویسان، از نسخههای مختلفی استفاده نشود و همچنین روند بهروز رسانی به سادگی انجام گیرد.
مزایای Docker برای برنامه نویسان
فرض کنید که یک App Service از Azure تهیه کرده باشید. تستهای unit, integration, acceptance را انجام داده و با خیال راحت Container خود را از طریق برای مثال Visual studio team service بر روی App service به صورت انتشار از طریق مدل Continuous Integration و Continuous Deployment داشته باشید. پس عملا داکر به Devops بودن محیط و چابک بودن تیم توسعه کمک شایانی کرده و فرآیندهای سخت و زمانبر انتقال Codeها از محیط توسعه به محیط انتشار را تسریع میبخشد.
بنابراین از داکر به راحتی میتوان در محیط Production نیز استفاده کرد و مزایای فوق العاده ای را برای برنامه نویسان ارائه کرده است. بطور مثال فرض کنید در تولید نرمافزار یک Web server ، تعدادی Database و یک Caching server که کانفیگ کردن، اجرا و ... به صورت عادی بسیار صعب و مشکل ساز بوده را به راحتی میتوان اجرا نمود. ضمن اینکه ممکن است هر کدام از ابزارهایی که استفاده شده، فقط مخصوص سیستم عاملی خاص باشد که قاعدتا احتیاج به بالا آوردن Virtual Machine خواهید بود و در سناریوهای خاصی مثل سیستم هایی با معماری Microservice که هر کدام از این ریز سرویسها ممکن است زبان، فریم ورک، دیتابیس و ... مخصوص به خود را داشته باشند، عملا کار بسیار سخت و پر هزینه خواهد بود (ضمن اینکه استفادهی همزمان از چند Virtual Machine در کنار هم در محیط توسعه، حجم زیادی از memory و disk سیستم شما را خواهد گرفت و شما را مجبور به ارتقای سیستم خود خواهد کرد!).
مشکل دیگری که Docker آن را حل کرده، Conflictهای ورژنهای مختلف ابزارهای مورد استفاده است. به راحتی میتوان Containerی از Imageها را به صورت ایزوله با ورژنهای مختلفی ایجاد کرد تا بطور کامل برنامه نویسان را از مشکل همیشگی بهروزرسانیها و Role-back کردنها آسوده خاطر نماید.
از آنجایی که داکر قابلیت اجرای در محیط production را نیز دارد، عملا محیط Development با محیط Production تفاوتی ندارد و این جملهی معروف که «در سیستم من کار میکند اما در نسخهی انتشار داده شده خیر» دیگر اتفاق نخواهد افتاد.
به راحتی میتوانید از یک Image خاص، Containerهای ایزولهی متفاوتی را ساخته و همگی آنها را در کنار هم اجرا نمود و مورد تست و ارزیابی قرار داد.
Dokcer hub
مخرنی است از هزاران Image آماده از قبیل سیستم عامل، فریم ورک و... که قابلیت استفادهی مجدد خواهد داشت. همچنین شما میتوانید Imageهای خود را نیز بدان اضافه نموده تا دیگران از آن استفاده نمایند. استفاده از مخزنهای public آن رایگان میباشد. از آنجایی که Docker یک محصول متن باز و رایگان است، یک بخش از درآمدهای آن از فروش اختصاصی مخزنها در DokcerHub میباشد (چیزی شبیه به Private Repository در Github).
بیشتر از این به مفاهیم نمیپردازیم. برای مطالعهی بیشتر، کتاب فوق العادهی Mastering Docker را پیشنهاد میکنم.
شروع به کار با Docker
بعد از نصب کردن نسخهی رسمی Docker و باز کردن ترمینال مربوطه، اولین دستوراتی را که باید با آن آشنا باشیم، شامل موارد زیر میباشد:
لیست Imageهای کش شدهی بر روی سیستم:
docker images
docker ps
برای آزمایش کردن و نصب اولین image، دستور زیر را وارد میکنیم (میتوانید اطلاعات بیشتری از imageها را در dockerHub پیدا کنید). من در اینجا kitematic/hello-world-nginx را به عنوان image از مخزن dokcerhub، بر روی سیستم خود pull کردهام (این یک نسخهی بسیار سبک از کانتینر nginx میباشد).
docker pull kitematic/hello-world-nginx
حال وقت اجرای این image و توزیع آن بر روی container میباشد که با استفاده از دستور زیر است:
docker run -p 80:80 kitematic/hello-world-nginx
بعد از اجرای این دستور میتوانید با وارد کردن ip مربوط به virtual machine ساخته شده بر روی سیستم خود (اگر از مک یا ویندوز استفاده میکنید احتمالا 192.168.99.100 خواهد بود) که البته با دستور docker-machine ip میتوانید آن را پیدا کنید و وارد کردن آن بر روی مرورگر خود، تصویری مثل زیر را مشاهده کنید:
بدین معناست که container شما اجرا شده و قابلیت مورد استفاده قرار گرفتن را خواهد داشت. حال اگر دستور docker ps را مجددا وارد نمایید، اطلاعات این container را از نوع id, status port و غیره، مشاهده خواهید کرد.
class Car { } class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; ... } }
class Transporter { public string Name { get; private set; } public Transporter(string name) { this.Name = name; } public void Deliver(Car[] cars, string town) { Console.WriteLine("Delivering {0} car(s) to {1} by {2}", cars.Length, town, this.Name); } }
static class TransporterLocator { static IList<Transporter> transporters = new List<Transporter>(); public static void Register(Transporter transporter) { transporters.Add(transporter); } public static Transporter Locate(string name) { return transporters .Where(transporter => transporter.Name == name) .Single(); } }
class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = null; if (carsCount <= 12) transporter = TransporterLocator.Locate("truck"); else transporter = TransporterLocator.Locate("train"); transporter.Deliver(cars, town); } }
TransporterLocator.Register(new Transporter("truck")); TransporterLocator.Register(new Transporter("train")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
TransporterLocator.Register(new Transporter("truck")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
class CarProducer { private Transporter truck; private Transporter train; public CarProducer(Transporter truck, Transporter train) { if (truck == null) throw new ArgumentNullException("truck"); if (train == null) throw new ArgumentNullException("train"); this.truck = truck; this.train = train; } public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = this.truck; if (carsCount > 12) transporter = this.train; transporter.Deliver(cars, town); } }
آیا وضعیتی وجود دارد که در آن Service Locator یک راه حل قابل قبول باشد؟
در برخی موارد بجای اینکه وابستگیها را به صورت صریح قید کنیم، بهتر است از این الگو استفاده کنیم.
فعالسازی بررسی مشکلات قالبهای کامپوننتها
برای فعالسازی بررسی مشکلات قالبهای کامپوننتها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
{ "compilerOptions": { "experimentalDecorators": true, ... }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "preserveWhiteSpace": false, ... } }
- البته این خاصیت در حین استفادهی از یکی از دستورات ng serve --aot و یا ng build --prod انتخاب میشود.
- مقدار این پرچم در نگارشهای 5x به صورت پیشفرض به false تنظیم شدهاست؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آنرا شروع کنید.
یک مثال: بررسی خاصیت fullTemplateTypeCheck
فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کردهاید که فقط دارای خاصیت name است:
export interface PonyModel { name: string; }
import { PonyModel } from "./pony"; @Component({ selector: "app-detect-common-errors-test", templateUrl: "./detect-common-errors-test.component.html", styleUrls: ["./detect-common-errors-test.component.css"] }) export class DetectCommonErrorsTestComponent implements OnInit { ponyModel: PonyModel = { name: "Pony1" };
<p>Hello {{ponyModel.age}}
در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
\detect-common-errors-test.component.html(5,4): : Property 'age' does not exist on type 'PonyModel'.
برای اینکه بتوانید به حداکثر کارآیی این قابلیت برسید، بهتر است گزینهی strict را در تنظیمات کامپایلر TypeScript روشن کنید و خودتان را به کار با نوعهای نال نپذیر عادت دهید. به این ترتیب میتوانید تعداد خطاهای احتمالی بیشتری را پیش از موعد و پیش از وقوع آنها در زمان اجرا، در زمان کامپایل، پیدا و رفع کنید.
یک نکتهی تکمیلی
افزونهی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارشهای پیش از 5 نیز قابل استفاده است).
مروری بر روش ها و رویکردهای مختلف در یادگیری مدل
همان گونه که اشاره شد در روشهای با ناظر (برای مثال الگوریتمهای دسته بندی) کل مجموعه دادهها به دو بخش مجموعه دادههای آموزشی و مجموعه دادههای آزمایشی تقسیم میشود. در مرحله یادگیری (آموزش) مدل، الگوریتم براساس مجموعه دادههای آموزشی یک مدل میسازد که شکل مدل ساخته شده به الگوریتم یادگیرنده مورد استفاده بستگی دارد. در مرحله ارزیابی براساس مجموعه دادههای آزمایشی دقت و کارائی مدل ساخته شده بررسی میشود. توجه داشته باشید که مجموعه دادههای آزمایشی برای مدل ساخته شده پیش از این ناشناخته هستند.
در مرحله یادگیری مدل؛ برای مقابله با مشکل به خاطرسپاری (Memorization) مجموعه دادههای آموزشی، در برخی موارد بخشی از مجموعه دادههای آموزشی را از آن مجموعه جدا میکنند که با عنوان مجموعه داده ارزیابی (Valid Dataset) شناسائی میشود. استفاده از مجموعه داده ارزیابی باعث میشود که مدل ساخته شده، مجموعه دادههای آموزشی را حقیقتاً یاد بگیرد و در پی به خاطرسپاری و حفظ آن نباشد. به بیان دیگر در مرحله یادگیری مدل؛ تا قبل از رسیدن به لحظه ای، مدل در حال یادگیری و کلی سازی (Generalization) است و از آن لحظه به بعد در حال به خاطرسپاری (Over Fitting) مجموعه دادههای آموزشی است. بدیهی است به خاطرسپاری باعث افزایش دقت مدل برای مجموعه دادههای آموزشی و بطور مشابه باعث کاهش دقت مدل برای مجموعه دادههای آزمایشی میشود. بدین منظور جهت جلوگیری از مشکل به خاطرسپاری از مجموعه داده ارزیابی استفاده میشود که به شکل غیر مستقیم در فرآیند یادگیری مدل، وارد عمل میشوند. بدین ترتیب مدلی که مفهومی را از دادههای آموزشی فرا گرفته، نسبت به مدلی که صرفاً دادههای آموزشی را به خوبی حفظ کرده است، برای مجموعه داده آزمایشی دقت به مراتب بالاتری دارد. این حقیقت در بیشتر فرآیندهای آموزشی که از مجموعه داده ارزیابی بهره میگیرند قابل مشاهده است.
در روشهای بدون ناظر یا روشهای توصیفی (برای مثال خوشه بندی) الگوریتمها فاقد مراحل آموزشی و آزمایشی هستند و در پایان عملیات یادگیری مدل، مدل ساخته شده به همراه کارائی آن به عنوان خروجی ارائه میشود، برای مثال در الگوریتمهای خوشه بندی خروجی همان خوشههای ایجاد شده هستند و یا خروجی در روش کشف قوانین انجمنی عبارت است از مجموعه ای از قوانین «اگر- آنگاه» که بیانگر ارتباط میان رخداد توامان مجموعه ای از اشیاء با یکدیگر میباشد.
در این قسمت عملیات ساخت مدل در فرآیند داده کاوی برای سه روش دسته بندی، خوشه بندی و کشف قوانین انجمنی ارائه میشود. بدیهی است برای هر کدام از این روشها علاوه بر الگوریتمهای معرفی شده، الگوریتمهای متنوعی دیگری نیز وجود دارد. در ادامه سعی میشود به صورت کلان به فلسفه یادگیری مدل پرداخته شود. فهرست مطالب به شرح زیر است:
1- دسته بندی:
1-1- دسته بندی مبتنی بر درخت تصمیم (Decision Tree based methods) :
1-2- دسته بندهای مبتنی بر قانون (Rule based methods) :
1-3- دسته بندهای مبتنی بر نظریه بیز (Naïve Bayes and Bayesian belief networks) :
2- خوشه بندی:
2-1- خوشه بندی افرازی (Centroid Based Clustering) :
2-1-1- الگوریتم خوشه بندی K-Means :
2-1-2- الگوریتم خوشه بندی K-Medoids :
2-1-3- الگوریتم خوشه بندی Bisecting K-Means :
2-1-4- الگوریتم خوشه بندی Fuzzy C-Means :
2-2- خوشه بندی سلسله مراتبی (Connectivity Based Clustering (Hierarchical Clustering :
2-2-1- روشهای خوشه بندی تجمیعی (Agglomerative Clustering) :
2-2-2- روشهای خوشه بندی تقسیمی (Divisive Clustering) :
2-3- خوشه بندی مبتنی بر چگالی (Density Based Clustering) :
3- کشف قوانین انجمنی :
3-1- الگوریتم های Apriori ، Brute-Force و FP-Growth:
1- دسته بندی:
در الگوریتمهای دسته بندی، برای هر یک از رکوردهای مجموعه داده مورد کاوش، یک برچسب که بیانگر حقیقتی از مساله است تعریف میشود و هدف الگوریتم یادگیری؛ یافتن نظم حاکم بر این برچسب هاست. به بیان دیگر در مرحله آموزش؛ مجموعه دادههای آموزشی به یکی از الگوریتمهای دسته بندی داده میشود تا بر اساس سایر ویژگیها برای مقادیر ویژگی دسته، مدل ساخته شود. سپس در مرحله ارزیابی؛ دقت مدل ساخته شده به کمک مجموعه دادههای آزمایشی ارزیابی خواهد شد. انواع گوناگون الگوریتمهای دسته بندی را میتوان بصورت ذیل برشمرد:
1-1- دسته بندی مبتنی بر درخت تصمیم (Decision Tree based methods):
از مشهورترین روشهای ساخت مدل دسته بندی میباشد که دانش خروجی را به صورت یک درخت از حالات مختلف مقادیر ویژگیها ارائه میکند. بدین ترتیب دسته بندیهای مبتنی بر درخت تصمیم کاملاً قابل تفسیر میباشند. در حالت کلی درخت تصمیم بدست آمده برای یک مجموعه داده آموزشی؛ واحد و یکتا نیست. به بیان دیگر براساس یک مجموعه داده، درختهای تصمیم مختلفی میتوان بدست آورد. عموماً به منظور فراهم نمودن اطلاعات بیشتری از داده ها، از میان ویژگیهای موجود یک Case ابتدا آنهایی که دارای خاصیت جداکنندگی بیشتری هستند انتخاب میشوند. در واقع براساس مجموعه دادههای آموزشی از میان ویژگی ها، یک ویژگی انتخاب میشود و در ادامه مجموعه رکوردها براساس مقدار این ویژگی شکسته میشود و این فرآیند ادامه مییابد تا درخت کلی ساخته شود. پس از ساخته شدن مدل، میتوان آن را بر روی مجموعه دادههای آزمایشی اعمال (Apply) نمود. منظور از اعمال کردن مدل، پیش بینی مقدار ویژگی یک دسته برای یک رکورد آزمایشی براساس مدل ساخته شده است. توجه شود هدف پیش بینی ویژگی دسته این رکورد، براساس درخت تصمیم موجود است.
بطور کلی الگوریتمهای تولید درخت تصمیم مختلفی از جمله SPRINT، SLIQ، C4.5، ID3، CART و HUNT وجود دارد. این الگوریتمها به لحاظ استفاده از روشهای مختلف جهت انتخاب ویژگی و شرط توقف در ساخت درخت با یکدیگر تفاوت دارند. عموماً الگوریتمهای درخت تصمیم برای شناسائی بهترین شکست، از یک مکانیزم حریصانه (Greedy) استفاده میکنند که براساس آن شکستی که توزیع دستهها در گرههای حاصل از آن همگن باشد، نسبت به سایر شکستها بهتر خواهد بود. منظور از همگن بودن گره این است که همه رکوردهای موجود در آن متعلق به یک دسته خاص باشند، بدین ترتیب آن گره به برگ تبدیل خواهد شد. بنابراین گره همگن گره ای است که کمترین میزان ناخالصی (Impurity) را دارد. به بیان دیگر هر چه توزیع دستهها در یک گره همگنتر باشد، آن گره ناخالصی کمتری خواهد داشت. سه روش مهم برای محاسبه ناخالصی گره وجود دارد که عبارتند از: ضریب GINI، روش Entropy و Classification Error.
از مزایای درخت تصمیم میتوان به توانایی کار با دادههای گسسته و پیوسته، سهولت در توصیف شرایط (با استفاده از منطق بولی) در درخت تصمیم، عدم نیاز به تابع تخمین توزیع، کشف روابط غیرمنتظره یا نامعلوم و ... اشاره نمود.
همچنین از معایب درخت تصمیم نسبت به دیگر روشهای داده کاوی میتوان این موارد را برشمرد: تولید درخت تصمیم گیری هزینه بالائی دارد، در صورت همپوشانی گرهها تعداد گرههای پایانی زیاد میشود، طراحی درخت تصمیم گیری بهینه دشوار است، احتمال تولید روابط نادرست وجود دارد و ... .
میتوان موارد استفاده از دسته بند درخت تصمیم نسبت به سایر دسته بندی کنندههای تک مرحله ای رایج را؛ حذف محاسبات غیر ضروری و انعطاف پذیری در انتخاب زیر مجموعههای مختلفی از صفات برشمرد. در نهایت از جمله مسائل مناسب برای یادگیری درخت تصمیم، میتوان به مسائلی که در آنها نمونهها به شکل جفتهای «صفت-مقدار» بازنمائی میشود و همچنین مسائلی که تابع هدف، مقادیر خروجی گسسته دارد اشاره نمود.
1-2- دسته بندهای مبتنی بر قانون (Rule based methods):
این دسته بندها دانش خروجی خود را به صورت یک مجموعه از قوانین «اگر-آنگاه» نشان میدهند. هر قانون یک بخش شرایط (LHS: Left Hand Side) و یک بخش نتیجه (RHS: Right Hand Side) دارد. بدیهی است اگر تمام شرایط مربوط به بخش مقدم یک قانون درباره یک رکورد خاص درست تعبیر شود، آن قانون آن رکورد را پوشش میدهد. دو معیار Accuracy و Coverage برای هر قانون قابل محاسبه است که هر چه میزان این دو معیار برای یک قانون بیشتر باشد، آن قانون؛ قانونی با ارزشتر محسوب میشود.
Coverage یک قانون، برابر با درصد رکوردهایی است که بخش شرایط قانون مورد نظر در مورد آنها صدق میکند و درست تعبیر میشود. بنابراین هر چه این مقدار بیشتر باشد آن قانون، قانونی کلیتر و عمومیتر میباشد.
Accuracy یک قانون بیان میکند که در میان رکوردهایی که بخش شرایط قانون در مورد آنها صدق میکند، چند درصد هر دو قسمت قانون مورد نظر در مورد آنها صحیح است.
چنانچه مجموعه همه رکوردها را در نظر بگیریم؛ مطلوبترین حالت این است که همواره یک رکورد توسط یک و تنها یک قانون پوشش داده شود، به بیان دیگر مجموعه قوانین نهایی به صورت جامع (Exhaustive Rules) و دو به دو ناسازگار (Mutually Exclusive Rules) باشند. جامع بودن به معنای این است که هر رکورد حداقل توسط یک قانون پوشش داده شود و معنای قوانین مستقل یا دو به دو ناسازگار بودن بدین معناست که هر رکورد حداکثر توسط یک قانون پوشش داده شود.
مجموعه قوانین و درخت تصمیم عیناً یک مجموعه دانش را نشان میدهند و تنها در شکل نمایش متفاوت از هم هستند. البته روشهای مبتنی بر قانون انعطاف پذیری و تفسیرپذیری بالاتری نسبت به روشهای مبتنی بر درخت دارند. همچنین اجباری در تعیین وضعیت هایی که در یک درخت تصمیم برای ترکیب مقادیر مختلف ویژگیها رخ میدهد ندارند و از این رو دانش خلاصهتری ارائه میدهند.
1-3- دسته بندهای مبتنی بر نظریه بیز (Naïve Bayes and Bayesian belief networks):
دسته بند مبتنی بر رابطه نظریه بیز (Naïve Bayes) از یک چهارچوب احتمالی برای حل مسائل دسته بندی استفاده میکند. براساس نظریه بیز رابطه I برقرار است:
هدف محاسبه دسته یک رکورد مفروض با مجموعه ویژگیهای (A1,A2,A3,…,An) میباشد. در واقع از بین دستههای موجود به دنبال پیدا کردن دسته ای هستیم که مقدار II را بیشینه کند. برای این منظور این احتمال را برای تمامی دستههای مذکور محاسبه نموده و دسته ای که مقدار این احتمال به ازای آن بیشینه شود را به عنوان دسته رکورد جدید در نظر میگیریم. ذکر این نکته ضروری است که بدانیم نحوه محاسبه برای ویژگیهای گسسته و پیوسته متفاوت میباشد.
2- خوشه بندی:
خوشه را مجموعه ای از دادهها که به هم شباهت دارند تعریف میکنند و هدف از انجام عملیات خوشه بندی فهم (Understanding) گروه رکوردهای مشابه در مجموعه دادهها و همچنین خلاصه سازی (Summarization) یا کاهش اندازهی مجموعه دادههای بزرگ میباشد. خوشه بندی از جمله روش هایی است که در آن هیچ گونه برچسبی برای رکوردها در نظر گرفته نمیشود و رکوردها تنها براساس معیار شباهتی که معرفی شده است، به مجموعه ای از خوشهها گروه بندی میشوند. عدم استفاده از برچسب موجب میشود الگوریتمهای خوشه بندی جزء روشهای بدون ناظر محسوب شوند و همانگونه که پیشتر ذکر آن رفت در خوشه بندی تلاش میشود تا دادهها به خوشه هایی تقسیم شوند که شباهت بین داده ای درون هر خوشه بیشینه و بطور مشابه شباهت بین دادهها در خوشههای متفاوت کمینه شود.
چنانچه بخواهیم خوشه بندی و دسته بندی را مقایسه کنیم، میتوان بیان نمود که در دسته بندی هر داده به یک دسته (طبقه) از پیش مشخص شده تخصیص مییابد ولی در خوشه بندی هیچ اطلاعی از خوشهها وجود ندارد و به عبارتی خود خوشهها نیز از دادهها استخراج میشوند. به بیان دیگر در دسته بندی مفهوم دسته در یک حقیقت خارجی نهفته است حال آنکه مفهوم خوشه در نهان فواصل میان رکورد هاست. مشهورترین تقسیم بندی الگوریتمهای خوشه بندی به شرح زیر است:
2-1- خوشه بندی افرازی (Centroid Based Clustering) :
تقسیم مجموعه دادهها به زیرمجموعههای بدون همپوشانی، به طریقی که هر داده دقیقاً در یک زیر مجموعه قرار داشته باشد. این الگوریتمها بهترین عملکرد را برای مسائل با خوشههای به خوبی جدا شده از خود نشان میدهند. از الگوریتمهای افرازی میتوان به موارد زیر اشاره نمود:
2-1-1- الگوریتم خوشه بندی K-Means :
در این الگوریتم عملاً مجموعه دادهها به تعداد خوشههای از پیش تعیین شده تقسیم میشوند. در واقع فرض میشود که تعداد خوشهها از ابتدا مشخص میباشند. ایده اصلی در این الگوریتم تعریف K مرکز برای هر یک از خوشهها است. بهترین انتخاب برای مراکز خوشهها قرار دادن آنها (مراکز) در فاصله هر چه بیشتر از یکدیگر میباشد. پس از آن هر رکورد در مجموعه داده به نزدیکترین مرکز خوشه تخصیص مییابد. معیار محاسبه فاصله در این مرحله هر معیاری میتواند باشد. این معیار با ماهیت مجموعه داده ارتباط تنگاتنگی دارد. مشهورترین معیارهای محاسبه فاصله رکوردها در روش خوشه بندی معیار فاصله اقلیدسی و فاصله همینگ میباشد. لازم به ذکر است در وضعیتی که انتخاب مراکز اولیه خوشهها به درستی انجام نشود، خوشههای حاصل در پایان اجرای الگوریتم کیفیت مناسبی نخواهند داشت. بدین ترتیب در این الگوریتم جواب نهائی به انتخاب مراکز اولیه خوشهها وابستگی زیادی دارد که این الگوریتم فاقد روالی مشخص برای محاسبه این مراکز میباشد. امکان تولید خوشههای خالی توسط این الگوریتم از دیگر معایب آن میباشد.
2-1-2- الگوریتم خوشه بندی K-Medoids :
این الگوریتم برای حل برخی مشکلات الگوریتم K-Means پیشنهاد شده است، که در آن بجای کمینه نمودن مجموع مجذور اقلیدسی فاصله بین نقاط (که معمولاً به عنوان تابع هدف در الگوریتم K-Means مورد استفاده قرار میگیرد)، مجموع تفاوتهای فواصل جفت نقاط را کمینه میکنند. همچنین بجای میانگین گیری برای یافتن مراکز جدید در هر تکرار حلقه یادگیری مدل، از میانه مجموعه اعضای هر خوشه استفاده میکنند.
2-1-3- الگوریتم خوشه بندی Bisecting K-Means :
ایده اصلی در این الگوریتم بدین شرح است که برای بدست آوردن K خوشه، ابتدا کل نقاط را به شکل یک خوشه در نظر میگیریم و در ادامه مجموعه نقاط تنها خوشه موجود را به دو خوشه تقسیم میکنیم. پس از آن یکی از خوشههای بدست آمده را برای شکسته شدن انتخاب میکنیم و تا زمانی که K خوشه را بدست آوریم این روال را ادامه میدهیم. بدین ترتیب مشکل انتخاب نقاط ابتدایی را که در الگوریتم K-Means با آن مواجه بودیم نداشته و بسیار کاراتر از آن میباشد.
2-1-4- الگوریتم خوشه بندی Fuzzy C-Means:
کارائی این الگوریتم نسبت به الگوریتم K-Means کاملاً بالاتر میباشد و دلیل آن به نوع نگاهی است که این الگوریتم به مفهوم خوشه و اعضای آن دارد. در واقع نقطه قوت الگوریتم Fuzzy C-Means این است که الگوریتمی همواره همگراست. در این الگوریتم تعداد خوشهها برابر با C بوده (مشابه الگوریتم K-Means) ولی برخلاف الگوریتم K-Means که در آن هر رکورد تنها به یکی از خوشههای موجود تعلق دارد، در این الگوریتم هر کدام از رکوردهای مجموعه داده به تمامی خوشهها متعلق است. البته این میزان تعلق با توجه به عددی که درجه عضویت تعلق هر رکورد را نشان میدهد، مشخص میشود. بدین ترتیب عملاً تعلق فازی هر رکورد به تمامی خوشهها سبب خواهد شد که امکان حرکت ملایم عضویت هر رکورد به خوشههای مختلف امکان پذیر شود. بنابراین در این الگوریتم امکان تصحیح خطای تخصیص ناصحیح رکوردها به خوشهها سادهتر میباشد و مهمترین نقطه ضعف این الگوریتم در قیاس با K-Means زمان محاسبات بیشتر آن میباشد. میتوان پذیرفت که از سرعت در عملیات خوشه بندی در برابر رسیدن به دقت بالاتر میتوان صرفه نظر نمود.
2-2- خوشه بندی سلسله مراتبی (Connectivity Based Clustering (Hierarchical Clustering:
در پایان این عملیات یک مجموعه از خوشههای تودرتو به شکل سلسله مراتبی و در قالب ساختار درختی خوشه بندی بدست میآید که با استفاده از نمودار Dendrogram چگونگی شکل گیری خوشههای تودرتو را میتوان نمایش داد. این نمودار درخت مانند، ترتیبی از ادغام و تجزیه را برای خوشههای تشکیل شده ثبت میکند، یکی از نقاط قوت این روش عدم اجبار برای تعیین تعداد خوشهها میباشد (بر خلاف خوشه بندی افرازی). الگوریتمهای مبتنی بر خوشه بندی سلسله مراتبی به دو دسته مهم تقسیم بندی میشوند:
2-2-1- روشهای خوشه بندی تجمیعی (Agglomerative Clustering) :
با نقاطی به عنوان خوشههای منحصر به فرد کار را آغاز نموده و در هر مرحله، به ادغام خوشههای نزدیک به یکدیگر میپردازیم، تا زمانی که تنها یک خوشه باقی بماند.
عملیات کلیدی در این روش، چگونگی محاسبه میزان مجاورت دو خوشه است و روشهای متفاوت تعریف فاصله بین خوشهها باعث تمایز الگوریتمهای مختلف مبتنی بر ایده خوشه بندی تجمیعی است. برخی از این الگوریتمها عبارتند از: خوشه بندی تجمیعی – کمینه ای، خوشه بندی تجمیعی – بیشینه ای، خوشه بندی تجمیعی – میانگینی، خوشه بندی تجمیعی – مرکزی.
2-2-2- روش های خوشه بندی تقسیمی (Divisive Clustering) :
با یک خوشهی دربرگیرندهی همه نقاط کار را آغاز نموده و در هر مرحله، خوشه را میشکنیم تا زمانی که K خوشه بدست آید و یا در هر خوشه یک نقطه باقی بماند.
2-3- خوشه بندی مبتنی بر چگالی (Density Based Clustering):
تقسیم مجموعه داده به زیرمجموعه هایی که چگالی و چگونگی توزیع رکوردها در آنها لحاظ میشود. در این الگوریتم مهمترین فاکتور که جهت تشکیل خوشهها در نظر گرفته میشود، تراکم و یا چگالی نقاط میباشد. بنابراین برخلاف دیگر روشهای خوشه بندی که در آنها تراکم نقاط اهمیت نداشت، در این الگوریتم سعی میشود تنوع فاصله هایی که نقاط با یکدیگر دارند، در عملیات خوشه بندی مورد توجه قرار گیرد. الگوریتم DBSCAN مشهورترین الگوریتم خوشه بندی مبتنی بر چگالی است.
به طور کلی عملکرد یک الگوریتم خوشه بندی نسبت به الگوریتمهای دیگر، بستگی کاملی به ماهیت مجموعه داده و معنای آن دارد.
3- کشف قوانین انجمنی :
الگوریتمهای کاشف قوانین انجمنی نیز همانند الگوریتمهای خوشه بندی به صورت روشهای توصیفی یا بدون ناظر طبقه بندی میشوند. در این الگوریتمها بدنبال پیدا کردن یک مجموعه از قوانین وابستگی یا انجمنی در میان تراکنشها (برای مثال تراکنشهای خرید در فروشگاه، تراکنشهای خرید و فروش سهام در بورس و ...) هستیم تا براساس قوانین کشف شده بتوان میزان اثرگذاری اشیایی را بر وجود مجموعه اشیاء دیگری بدست آورد. خروجی در این روش کاوش، به صورت مجموعه ای از قوانین «اگر-آنگاه» است، که بیانگر ارتباطات میان رخداد توامان مجموعه ای از اشیاء با یکدیگر میباشد. به بیان دیگر این قوانین میتواند به پیش بینی وقوع یک مجموعه اشیاء مشخص در یک تراکنش، براساس وقوع اشیاء دیگر موجود در آن تراکنش بپردازد. ذکر این نکته ضروری است که بدانیم قوانین استخراج شده تنها استلزام یک ارتباط میان وقوع توامان مجموعه ای از اشیاء را نشان میدهد و در مورد چرایی یا همان علیت این ارتباط سخنی به میان نمیآورد. در ادامه به معرفی مجموعه ای از تعاریف اولیه در این مبحث میپردازیم (در تمامی تعاریف تراکنشهای سبد خرید مشتریان در یک فروشگاه را به عنوان مجموعه داده مورد کاوش در نظر بگیرید):
• مجموعه اشیاء: مجموعه ای از یک یا چند شیء. منظور از مجموعه اشیاء K عضوی، مجموعه ای است که شامل K شیء باشد.
برای مثال:{مسواک، نان، شیر}
• تعداد پشتیبانی (Support Count) : فراوانی وقوع مجموعهی اشیاء در تراکنشهای موجود که آنرا با حرف σ نشان میدهیم.
برای مثال: 2=({مسواک، نان، شیر})σ
• مجموعه اشیاء مکرر (Frequent Item Set) : مجموعه ای از اشیاء که تعداد پشتیبانی آنها بزرگتر یا مساوی یک مقدار آستانه (Min Support Threshold) باشد، مجموعه اشیاء مکرر نامیده میشود.
• قوانین انجمنی: بیان کننده ارتباط میان اشیاء در یک مجموعه از اشیاء مکرر. این قوانین معمولاً به شکل X=>Y هستند.
برای مثال:{نوشابه}<={مسواک، شیر}
مهمترین معیارهای ارزیابی قوانین انجمنی عبارتند از:
• Support: کسری از تراکنشها که حاوی همه اشیاء یک مجموعه اشیاء خاص هستند و آنرا با حرف S نشان میدهند.
برای مثال: 2.2=({نان، شیر})S
• Confidence: کسری از تراکنشهای حاوی همه اشیاء بخش شرطی قانون انجمنی که صحت آن قانون را نشان میدهد که با آنرا حرف C نشان میدهند. برخلاف Support نمیتوانیم مثالی برای اندازه گیری Confidence یک مجموعه اشیاء بیاوریم زیرا این معیار تنها برای قوانین انجمنی قابل محاسبه است.
با در نظر گرفتن قانون X=>Y میتوان Support را کسری از تراکنش هایی دانست که شامل هر دو مورد X و Y هستند و Confidence برابر با اینکه چه کسری از تراکنش هایی که Y را شامل میشوند در تراکنش هایی که شامل X نیز هستند، ظاهر میشوند. هدف از کاوش قوانین انجمنی پیدا کردن تمام قوانین Rx است که از این دستورات تبعیت میکند:
در این دستورات منظور از SuppMIN و ConfMIN به ترتیب عبارت است از کمترین مقدار برای Support و Confidence که بایست جهت قبول هر پاسخ نهائی به عنوان یک قانون با ارزش مورد توجه قرار گیرد. کلیه قوانینی که از مجموعه اشیاء مکرر یکسان ایجاد میشوند دارای مقدار Support مشابه هستند که دقیقاً برابر با تعداد پشتیبانی یا همان σ شیء مکرری است که قوانین انجمنی با توجه به آن تولید شده اند. به همین دلیل فرآیند کشف قوانین انجمنی را میتوان به دو مرحله مستقل «تولید مجموعه اشیاء مکرر» و «تولید قوانین انجمنی مطمئن» تقسیم نمائیم.
در مرحله نخست، تمام مجموعه اشیاء که دارای مقدار Support ≥ SuppMIN میباشند را تولید میکنیم. رابطه I
در مرحله دوم با توجه به مجموعه اشیاء مکرر تولید شده، قوانین انجمنی با اطمینان بالا بدست میآیند که همگی دارای شرط Confidence ≥ ConfMIN هستند. رابطه II
3-1- الگوریتم های Apriori ، Brute-Force و FP-Growth:
یک روش تولید اشیاء مکرر روش Brute-Force است که در آن ابتدا تمام قوانین انجمنی ممکن لیست شده، سپس مقادیر Support و Confidence برای هر قانون محاسبه میشود. در نهایت قوانینی که از مقادیر آستانهی SuppMIN و ConfMIN تبعیت نکنند، حذف میشوند. تولید مجموعه اشیاء مکرر بدین طریق کاری بسیار پرهزینه و پیچیده ای میباشد، در واقع روشهای هوشمندانه دیگری وجود دارد که پیچیدگی بالای روش Brute-Force را ندارند زیرا کل شبکه مجموعه اشیاء را به عنوان کاندید در نظر نمیگیرند. همانند تولید مجموعه اشیاء مکرر، تولید مجموعه قوانین انجمنی نیز بسیار پرهزینه و گران است.
چنانچه یک مجموعه اشیاء مکرر مشخص با d شیء را در نظر بگیریم، تعداد کل قوانین انجمنی قابل استخراج از رابطه III محاسبه میشود. (برای مثال تعداد قوانین انجمنی قابل استخراج از یک مجموعه شیء 6 عضوی برابر با 602 قانون میباشد، که با توجه به رشد d؛ سرعت رشد تعداد قوانین انجمنی بسیار بالا میباشد.)
الگوریتمهای متعددی برای تولید مجموعه اشیاء مکرر وجود دارد برای نمونه الگوریتمهای Apriori و FP-Growth که در هر دوی این الگوریتم ها، ورودی الگوریتم لیست تراکنشها و پارامتر SuppMIN میباشد. الگوریتم Apriori روشی هوشمندانه برای یافتن مجموعه اشیاء تکرار شونده با استفاده از روش تولید کاندید است که از یک روش بازگشتی برای یافتن مجموعه اشیاء مکرر استفاده میکند. مهمترین هدف این الگوریتم تعیین مجموعه اشیاء مکرری است که تعداد تکرار آنها حداقل برابر با SuppMIN باشد. ایده اصلی در الگوریتم Apriori این است که اگر مجموعه اشیایی مکرر باشد، آنگاه تمام زیر مجموعههای آن مجموعه اشیاء نیز باید مکرر باشند. در واقع این اصل همواره برقرار است زیرا Support یک مجموعه شیء هرگز بیشتر از Support زیرمجموعههای آن مجموعه شیء نخواهد بود. مطابق با این ایده تمام ابرمجموعههای مربوط به مجموعه شیء نامکرر از شبکه مجموعه اشیاء حذف خواهند شد (هرس میشوند). هرس کردن مبتنی بر این ایده را هرس کردن بر پایه Support نیز عنوان میکنند که باعث کاهش قابل ملاحظه ای از تعداد مجموعههای کاندید جهت بررسی (تعیین مکرر بودن یا نبودن مجموعه اشیاء) میشود.
الگوریتم FP-Growth در مقایسه با Apriori روش کارآمدتری برای تولید مجموعه اشیاء مکرر ارائه میدهد. این الگوریتم با ساخت یک درخت با نام FP-Tree سرعت فرآیند تولید اشیاء مکرر را به طور چشمگیری افزایش میدهد، در واقع با یکبار مراجعه به مجموعه تراکنشهای مساله این درخت ساخته میشود. پس از ساخته شدن درخت با توجه به ترتیب نزولی Support مجموعه اشیاء تک عضوی (یعنی مجموعه اشیاء) مساله تولید مجموعه اشیاء مکرر به چندین زیر مسئله تجزیه میشود، که هدف در هر کدام از این زیر مساله ها، یافتن مجموعه اشیاء مکرری است که به یکی از آن اشیاء ختم خواهند شد.
الگوریتم Aprior علاوه بر تولید مجموعه اشیاء مکرر، اقدام به تولید مجموعه قوانین انجمنی نیز مینماید. در واقع این الگوریتم با استفاده از مجموعه اشیاء مکرر بدست آمده از مرحله قبل و نیز پارامتر ConfMIN قوانین انجمنی مرتبط را که دارای درجه اطمینان بالائی هستند نیز تولید میکند. به طور کلی Confidence دارای خصوصیت هماهنگی (Monotone) نیست ولیکن Confidence قوانینی که از مجموعه اشیاء یکسانی بوجود میآیند دارای خصوصیت ناهماهنگی هستند. بنابراین با هرس نمودن کلیه ابرقوانین انجمنی یک قانون انجمنی یا Confidence (Rx) ≥ ConfMIN در شبکه قوانین انجمنی (مشابه با شبکه مجموعه اشیاء) اقدام به تولید قوانین انجمنی مینمائیم. پس از آنکه الگوریتم با استفاده از روش ذکر شده، کلیه قوانین انجمنی با اطمینان بالا را در شبکه قوانین انجمنی یافت، اقدام به الحاق نمودن آن دسته از قوانین انجمنی مینماید که پیشوند یکسانی را در توالی قانون به اشتراک میگذارند و بدین ترتیب قوانین کاندید تولید میشوند.
جهت آشنائی بیشتر به List of machine learning concepts مراجعه نمائید.
بنابراین برای ایجاد یک کامپوننت میتوانیم به اینصورت عمل کنیم:
var app = angular.module("dntModule", []); app.component("pmApp", { template: `Hello this is a simple component` });
همانطور که مشاهده میکنید تابع component دو پارامتر را از ورودی دریافت خواهد کرد؛ نام کامپوننت و یک شیء برای تعیین تنظیمات کامپوننت. نام کامپوننت در اینجا به صورت camel case تعریف شده است؛ که در واقع یک convention برای Angular است. در اینحالت برای استفادهی از کامپوننت باید به اینصورت عمل کنیم:
<pm-app></pm-app>
در قسمت تنظیمات کامپوننت، در سادهترین حالت یک template تعیین شدهاست که بیانگر نحوهی رندر شدن یک کامپوننت میباشد. در اینحالت وقتی انگیولار به تگ فوق برسد، یک کامپوننت با نام pmApp را بارگذاری خواهد کرد.
ایجاد یک کامپوننت ساده
در ادامه میخواهیم یک کامپوننت ساده را جهت نمایش یکسری URL درون صفحه طراحی کنیم. ساختار صفحه index.html به صورت زیر خواهد بود:
<html ng-app="DNT"> <head> <meta charset="UTF-8"> <title>Using Angular Component</title> <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"> <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css"> </head> <body> <div class="container"> <div class="row"> <div class="col-md-3"> <dnt-widget></dnt-widget> </div> </div> </div> <script src="bower_components/angular/angular.js"></script> <script src="scripts/app.js"></script> <script src="scripts/components/dnt-widget.component.js"></script> </body> </html>
در اینجا ابتدا توسط دایرکتیو ng-app، به Angular، ماژولمان را معرفی کردهایم. سپس مداخل بوتاسترپ و کتابخانهی font-awesome را مشاهده میکنید. در ادامه، کتابخانهی Angular و همچنین فایل app.js جهت معرفی ماژول برنامه معرفی شدهاست. در نهایت نیز یک فایل در مسیر ذکر شده برای قرار دادن کدهای کامپوننت در مسیر scripts/components اضافه شدهاست.
همانطور که ملاحظه میکنید، کامپوننتمان به صورت یک تگ سفارشی، درون صفحه قرار گرفته است:
<dnt-archive></dnt-archive>
در ادامه باید به Angular، نحوهی تعریف این کامپوننت را اعلام کنیم. بنابراین یک فایل جاوا اسکریپتی را با نام dnt-widget.component، با محتویات زیر ایجاد کنید:
(function () { "use strict"; var app = angular.module("DNT"); function DntArchiveController() { var model = this; model.panel = { title: "Panel Title", items: [ { title: "Dotnettips", url: "https://www.dntips.ir" }, { title: "Google", url: "http://www.google.con" }, { title: "Yahoo", url: "http://www.yahoo.con" } ] }; }; app.component("dntWidget", { templateUrl: '/scripts/components/dnt-widget.component.html', controllerAs: "model", controller: DntArchiveController }); } ());
توضیح کدهای فوق:
همانطور که مشاهده میکنید، برای پارمتر دوم کامپوننت، سه پراپرتی را تعیین کردهایم:
templateUrl: به کمک این پراپرتی به Angular گفتهایم که محتوای قالب این کامپوننت، درون یک فایل HTML مجزا قرار دارد و به صورت linked template میباشد.
controllerAs: یکی از مزایای استفاده از کامپوننتها، استفاده از controller as syntax میباشد. لازم به ذکر است اگر این پراپرتی را مقداردهی نکنیم، به صورت پیشفرض مقدار ctrl$ در نظر گرفته خواهد شد.
controller: مزیت دیگر کامپوننتها، استفاده از کنترلرها است. با استفاده از این پراپرتی، یک کنترلر را برای کامپوننتمان رجیستر کردهایم. در نتیجه زمانیکهی Angular میخواهد کامپوننتمان را نمایش دهد، تابع تعریف شده برای این پراپرتی، جهت ایجاد یک controller instance فراخوانی خواهد شد. بنابراین هر پراپرتی یا تابعی که برای این controller instance تعریف کنیم، به راحتی درون ویوی آن جهت اعمال بایندینگ در دسترس خواهد بود (در نتیجه نیازی به scope$ نخواهد بود).
درون کنترلر نیز برای راحتی کار و همچنین به عنوان یک best practice، مقدار this را توسط یک متغیر با نام model، کپچر کردهایم. در اینجا یک شیء را با نام panel نیز به مدل اضافه کردهایم.
محتویات تمپلیت:
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"> <span class="fa fa-archive"></span> {{ model.panel.title}} </h3> </div> <ul class="list-group"> <li class="list-group-item" ng-repeat="item in model.panel.items"> <span class="fa fa-industry"></span> <a href="{{ item.url }}">{{ item.title }}</a> </li> </ul> </div>
ویوی کامپوننت پیچیدگی خاصی ندارد. همانطور که مشاهده میکنید یک پنل بوتاسترپی را ایجاد کردهایم که مقدار عنوان آن و همچنین آیتمهای آن، از شیء اتچ شده به مدل دریافت خواهند شد. بنابراین اکنون اگر برنامه را اجرا کنید، خروجی کامپوننت را به اینصورت مشاهده خواهید کرد:
همانطور که مشاهده میکنید استفاده از کامپوننتها در Angular 1.5 در مقایسه با ایجاد دایرکتیوها و کنترلرها خیلی سادهتر است. در واقع امکانات این API جدید تنها به مثال فوق ختم نمیشود؛ بلکه این API یک سیستم مسیریابی جدید را نیز معرفی کرده است که در قسمتهای بعدی به آن نیز خواهیم پرداخت.
جهت تکمیل بحث نیز یک تقویم شمسی ساده را در اینجا قرار دادهام. میتوانید جهت مرور بحث جاری به کدهای آن مراجعه کنید. البته هدف از تعریف این پروژه تنها یک مثال ساده برای معرفی کامپوننتها بود و طبیعتاً باگهای زیادی دارد. اگر مایل بودید میتوانید در توسعهی آن مشارکت نمائید.
کدهای این قسمت را نیز از اینجا میتوانید دریافت کنید.
به یک حداقل وب سروری نیاز دارد. برای مثال دستور ng serve -o یک وب سرور آزمایشی را بر روی پورت 4200 ایجاد میکند؛ برای اجرا و ارائهی برنامه. اجرای مستقیم index.html بدون وب سرور، یعنی پروتکل //:file و این پروتکل دسترسی محدودی را برای اجرای هر نوع برنامهی وب و یا بارگذاری فایلها را در مرورگر دارد. به همین جهت به پروتکل //:http نیاز خواهید داشت و بله ... این مورد هیچ نوع وابستگی به نوع وب سرور ندارد. همینقدر که توانایی پردازش پروتکل http را داشته باشد کافی است.
«... مواردی مانند مسیریابی ...»
در این مورد مفصل در سری مسیریابی برنامههای Angular بحث شدهاست و دو قسمت تنظیمات سمت سرور و سمت کلاینت را به همراه دارد.
این برنامه از چهار کامپوننت تشکیل شدهاست:
- کامپوننت App که در برگیرندهی سه کامپوننت زیر است:
- کامپوننت BasketItemsCounter: جمع تعداد آیتمهای انتخابی توسط کاربر را نمایش میدهد؛ به همراه دکمهای برای خالی کردن لیست انتخابی.
- کامپوننت ShopItemsList: لیست محصولات موجود در فروشگاه را نمایش میدهد. با کلیک بر روی هر آیتم آن، آیتم انتخابی به لیست انتخابهای او اضافه خواهد شد.
- کامپوننت BasketItemsList: لیستی را نمایش میدهد که حاصل انتخابهای کاربر در کامپوننت ShopItemsList است (یا همان سبد خرید). در ذیل این لیست، جمع نهایی قیمت قابل پرداخت نیز درج میشود. همچنین اگر کاربر بر روی دکمهی remove هر ردیف کلیک کند، یک واحد از چند واحد انتخابی، حذف خواهد شد.
بنابراین در اینجا سه کامپوننت مجزا را داریم که با هم تبادل اطلاعات میکنند. یکی جمع تعداد محصولات خریداری شده را، دیگری لیست محصولات موجود را و آخری لیست خرید نهایی را نمایش میدهد. همچنین این سه کامپوننت، فرزند یک دیگر هم محسوب نمیشوند و انتقال اطلاعات بین اینها نیاز به بالا بردن state هر کدام و قرار دادن آنها در کامپوننت App را دارد تا بتوان پس از آن از طریق props آنها را بین سه کامپوننت فوق که اکنون فرزند کامپوننت App محسوب میشوند، به اشتراک گذاشت. روش بهتر اینکار، استفاده از یک مخزن حالت سراسری است تا حالتهای این کامپوننتها را نگهداری کرده و دادهها را بین آنها به اشتراک بگذارد که در اینجا برای حل این مساله از کتابخانههای mobx و mobx-react استفاده خواهیم کرد.
برپایی پیشنیازها
برای پیاده سازی برنامهی فوق، یک پروژهی جدید React را ایجاد میکنیم:
> create-react-app state-management-with-mobx-part4 > cd state-management-with-mobx-part4
> npm install --save bootstrap mobx mobx-react mobx-react-devtools mobx-state-tree
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود.
- اصل کار برنامه توسط دو کتابخانهی mobx و کتابخانهی متصل کنندهی آن به برنامههای react که mobx-react نام دارد، انجام خواهد شد.
- چون میخواهیم از افزونهی mobx-devtools نیز استفاده کنیم، نیاز است دو بستهی mobx-react-devtools و همچنین mobx-state-tree را که جزو وابستگیهای آن است، نصب کنیم.
سپس بستههای زیر را که در قسمت devDependencies فایل package.json درج خواهند شد، باید نصب شوند:
> npm install --save-dev babel-eslint customize-cra eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort react-app-rewired typescript
- افزودن فایل جدید config-overrides.js به ریشهی پروژه، تا پشتیبانی ازlegacy" decorators spec" فعال شود.
- اصلاح فایل package.json و ویرایش قسمت scripts آن برای استفادهی از react-app-rewired، تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود.
- همچنین فایل غنی شدهی eslintrc.json. را نیز به ریشهی پروژه اضافه میکنیم.
تهیه سرویس لیست محصولات موجود در فروشگاه
این برنامه از یک لیست درون حافظهای، برای تهیهی لیست محصولات موجود در فروشگاه استفاده میکند. به همین جهت پوشهی service را افزوده و سپس فایل جدید src\services\productsService.js را با محتوای زیر، ایجاد میکنیم:
const products = [ { id: 1, name: "Item 1", price: 850 }, { id: 2, name: "Item 2", price: 900 }, { id: 3, name: "Item 3", price: 1500 }, { id: 4, name: "Item 4", price: 1000 } ]; export default products;
ایجاد کامپوننت نمایش لیست محصولات
پس از مشخص شدن لیست محصولات قابل فروش، کامپوننت جدید src\components\ShopItemsList.jsx را به صورت زیر ایجاد میکنیم:
import React from "react"; import products from "../services/productsService"; const ShopItemsList = ({ onAdd }) => { return ( <table className="table table-hover"> <thead className="thead-light"> <tr> <th>Name</th> <th>Price</th> <th>Action</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td> <button className="btn btn-sm btn-info" onClick={() => onAdd(product)} > Add </button> </td> </tr> ))} </tbody> </table> ); }; export default ShopItemsList;
- در اینجا همچنین هر ردیف، به همراه یک دکمهی Add نیز هست که قرار است با کلیک بر روی آن، متد رویدادگردان onAdd فراخوانی شود. این متد نیز از طریق props این کامپوننت دریافت میشود. کتابخانههای مدیریت حالت، تمام خواص و رویدادگردانهای مورد نیاز یک کامپوننت را از طریق props، تامین میکنند.
- فعلا این کامپوننت به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
ایجاد کامپوننت نمایش لیست خرید کاربر (سبد خرید)
اکنون که میتوان توسط کامپوننت لیست محصولات، تعدادی از آنها را خریداری کرد، کامپوننت جدید src\components\BasketItemsList.jsx را برای نمایش لیست نهایی خرید کاربر، به صورت زیر پیاده سازی میکنیم:
import React from "react"; const BasketItemsList = ({ items, totalPrice, onRemove }) => { return ( <> <table className="table table-hover"> <thead className="thead-light"> <tr> <th>Name</th> <th>Price</th> <th>Count</th> <th>Action</th> </tr> </thead> <tbody> {items.map(item => ( <tr key={item.id}> <td>{item.name}</td> <td>{item.price}</td> <td>{item.count}</td> <td> <button className="btn btn-sm btn-danger" onClick={() => onRemove(item.id)} > Remove </button> </td> </tr> ))} <tr> <td align="right"> <strong>Total: </strong> </td> <td> <strong>{totalPrice}</strong> </td> <td></td> <td></td> </tr> </tbody> </table> </> ); }; export default BasketItemsList;
const BasketItemsList = ({ items, totalPrice, onRemove }) => {
- همچنین هر ردیف نمایش داده شده، به همراه یک دکمهی Remove آیتم انتخابی نیز هست که به متد رویدادگردان onRemove متصل شدهاست.
- در ردیف انتهایی این لیست، مقدار totalPrice که یک خاصیت محاسباتی است، درج میشود.
- فعلا این کامپوننت نیز به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
ایجاد کامپوننت نمایش تعداد آیتمهای خریداری شده
کاربر اگر آیتمی را از لیست محصولات انتخاب کند و یا محصول انتخاب شده را از لیست خرید حذف کند، تعداد نهایی باقی مانده را میتوان در کامپوننت src\components\BasketItemsCounter.jsx مشاهده کرد:
import React, { Component } from "react"; class BasketItemsCounter extends Component { render() { const { count, onRemoveAll } = this.props; return ( <div> <h1>Total items: {count}</h1> <button type="button" className="btn btn-sm btn-danger" onClick={() => onRemoveAll()} > Empty Basket </button> </div> ); } } export default BasketItemsCounter;
- فعلا این کامپوننت نیز به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
نمایش ابتدایی سه کامپوننت توسط کامپوننت App
اکنون که این سه کامپوننت تکمیل شدهاند، میتوان المانهای آنها را در فایل src\App.js درج کرد تا در صفحه نمایش داده شوند:
import React, { Component } from "react"; import BasketItemsCounter from "./components/BasketItemsCounter"; import BasketItemsList from "./components/BasketItemsList"; import ShopItemsList from "./components/ShopItemsList"; class App extends Component { render() { return ( <main className="container"> <div className="row"> <BasketItemsCounter /> </div> <hr /> <div className="row"> <h2>Products</h2> <ShopItemsList /> </div> <div className="row"> <h2>Basket</h2> <BasketItemsList /> </div> </main> ); } } export default App;
میتوان همانند Redux کل state برنامه را داخل یک شیء store ذخیره کرد و یا چون در اینجا میتوان طراحی مخزن حالت MobX را به دلخواه انجام داد، میتوان چندین مخزن حالت را تهیه و به هم متصل کرد؛ مانند تصویری که مشاهده میکنید. در اینجا:
- src\stores\counter.js: مخزن دادهی حالت کامپوننت شمارشگر است.
- src\stores\market.js: مخزن دادهی کامپوننتهای لیست محصولات و سبد خرید است.
- src\stores\index.js: کار ترکیب دو مخزن قبل را انجام میدهد.
در ادامه کدهای کامل این مخازن را مشاهده میکنید:
مخزن حالت src\stores\counter.js
import { action, observable } from "mobx"; export default class CounterStore { @observable totalNumbersInBasket = 0; constructor(rootStore) { this.rootStore = rootStore; } @action increase = () => { this.totalNumbersInBasket++; }; @action decrease = () => { this.totalNumbersInBasket--; }; }
- در اینجا خاصیت totalNumbersInBasket به صورت observable تعریف شدهاست و با تغییر آن چه به صورت مستقیم، با مقدار دهی آن و یا توسط دو action تعریف شده، سبب به روز رسانی UI خواهد شد.
- میشد این مخزن را با مخزن src\stores\market.js یکی کرد؛ اما جهت ارائهی مثالی در مورد نحوهی تعریف چند مخزن و روش برقراری ارتباط بین آنها، به صورت مجزایی تعریف شد.
مخزن حالت src\stores\market.js
import { action, computed, observable } from "mobx"; export default class MarketStore { @observable basketItems = []; constructor(rootStore) { this.rootStore = rootStore; } @action add = product => { const selectedItem = this.basketItems.find(item => item.id === product.id); if (selectedItem) { selectedItem.count++; } else { this.basketItems.push({ ...product, count: 1 }); } this.rootStore.counterStore.increase(); }; @action remove = id => { const selectedItem = this.basketItems.find(item => item.id === id); selectedItem.count--; if (selectedItem.count === 0) { this.basketItems.remove(selectedItem); } this.rootStore.counterStore.decrease(); }; @action removeAll = () => { this.basketItems = []; this.rootStore.counterStore.totalNumbersInBasket = 0; }; @computed get totalPrice() { return this.basketItems.reduce((previous, current) => { return previous + current.price * current.count; }, 0); } }
- توسط متد add آن در کامپوننت نمایش لیست محصولات، میتوان آیتمی را به این آرایه اضافه کرد. در اینجا چون شیء product مورد استفاده دارای خاصیت count نیست، روش افزودن آنرا توسط spread operator برای درج خواص شیء product اصلی و سپس تعریف آنرا مشاهده میکنید. این فراخوانی، سبب افزایش یک واحد به عدد شمارشگر نیز میشود.
- متد remove آن در کامپوننت سبد خرید، مورد استفاده قرار میگیرد تا کاربر بتواند اطلاعاتی را از این لیست حذف کند. این فراخوانی، سبب کاهش یک واحد از عدد شمارشگر نیز میشود.
- متد removeAll آن در کامپوننت شمارشگر بالای صفحه استفاده میشود تا سبب خالی شدن آرایهی آیتمهای انتخابی گردد و همچنین عدد آنرا نیز صفر کند.
- خاصیت محاسباتی totalPrice آن در پایین جدول سبد خرید، جمع کل هزینهی قابل پرداخت را مشخص میکند.
مخزن حالت src\stores\index.js
در اینجا روش یکی کردن دو مخزن حالت یاد شده را به صورت خاصیتهای عمومی یک مخزن کد ریشه، مشاهده میکنید:
import CounterStore from "./counter"; import MarketStore from "./market"; class RootStore { counterStore = new CounterStore(this); marketStore = new MarketStore(this); } export default RootStore;
export default class MarketStore { @observable basketItems = []; constructor(rootStore) { this.rootStore = rootStore; } @action removeAll = () => { this.basketItems = []; this.rootStore.counterStore.totalNumbersInBasket = 0; }; }
پس از ایجاد مخازن حالت، اکنون نیاز است آنها را در اختیار سلسه مراتب کامپوننتهای برنامه قرار دهیم. به همین جهت به فایل src\index.js مراجعه کرده و آنرا به صورت زیر تغییر میدهیم:
import "./index.css"; import "bootstrap/dist/css/bootstrap.css"; import makeInspectable from "mobx-devtools-mst"; import { Provider } from "mobx-react"; import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import RootStore from "./stores"; const rootStore = new RootStore(); if (process.env.NODE_ENV === "development") { makeInspectable(rootStore); // https://github.com/mobxjs/mobx-devtools } ReactDOM.render( <Provider {...rootStore}> <App /> </Provider>, document.getElementById("root") ); serviceWorker.unregister();
- سپس یک وهلهی جدید از RootStore را که حاوی خاصیتهای عمومی counterStore و marketStore است، ایجاد میکنیم.
- اگر علاقمند باشید تا حین کار با MobX، جزئیات پشت صحنهی آنرا توسط افزونهی mobx-devtools ردیابی کنید، روش آنرا در اینجا با فراخوانی متد makeInspectable مشاهده میکنید. مقدار process.env.NODE_ENV نیز بر اساس پروسهی جاری node.js اجرا کنندهی برنامهی React تامین میشود. اطلاعات بیشتر
- قسمت آخر این تنظیمات، محصور کردن کامپوننت App که بالاترین کامپوننت در سلسله مراتب کامپوننتهای برنامه است، با شیء Provider میباشد. در این شیء توسط spread operator، سبب درج خواص عمومی rootStore، به عنوان مخازن قابل استفاده شدهایم. تنظیم {rootStore...} معادل عبارت زیر است:
<Provider counterStore={rootStore.counterStore} marketStore={rootStore.marketStore}>
اتصال کامپوننت ShopItemsList به مخزن حالت marketStore
پس از ایجاد rootStore و محصور کردن کامپوننت App توسط شیء Provider در فایل src\index.js، اکنون باید قسمت export default کامپوننتهای برنامه را جهت استفادهی از مخازن حالت، یکی یکی ویرایش کرد:
import { inject, observer } from "mobx-react"; import React from "react"; import products from "../services/productsService"; const ShopItemsList = ({ onAdd }) => { return ( // ... ); }; export default inject(({ marketStore }) => ({ onAdd: marketStore.add }))(observer(ShopItemsList));
اتصال کامپوننت BasketItemsList به مخزن حالت marketStore
در اینجا نیز سطر export default را جهت دریافت خاصیت marketStore، از شیء Provider تامین شدهی در فایل src\index.js، ویرایش میکنیم. به این ترتیب سه props مورد انتظار این کامپوننت، توسط خاصیتهای basketItems (آرایهی اشیاء انتخابی توسط کاربر)، totalPrice (خاصیت محاسباتی جمع کل هزینه) و متد رویدادگردان onRemove (برای حذف یک آیتم) تامین میشوند. در آخر کامپوننت را به صورت observer محصور کرده و بازگشت میدهیم تا تغییرات در مخزن حالت آن، سبب به روز رسانی UI آن شوند:
import { inject, observer } from "mobx-react"; import React from "react"; const BasketItemsList = ({ items, totalPrice, onRemove }) => { return ( // ... ); }; export default inject(({ marketStore }) => ({ items: marketStore.basketItems, totalPrice: marketStore.totalPrice, onRemove: marketStore.remove }))(observer(BasketItemsList));
اتصال کامپوننت BasketItemsCounter به دو مخزن حالت counterStore و marketStore
در اینجا روش استفادهی از decorator syntax کتابخانهی mobx-react را بر روی یک کامپوننت کلاسی مشاهده میکنید. تزئین کنندهی inject، امکان دسترسی به مخازن حالت تزریقی به شیء Provider را میسر کرده و سپس توسط آن میتوان props مورد انتظار کامپوننت را از مخازن متناظر استخراج کرده و در اختیار کامپوننت قرار داد. همچنین این کامپوننت توسط تزئین کنندهی observer نیز علامت گذاری شدهاست. در این حالت نیازی به تغییر سطر export default نیست.
import { inject, observer } from "mobx-react"; import React, { Component } from "react"; @inject(rootStore => ({ count: rootStore.counterStore.totalNumbersInBasket, onRemoveAll: rootStore.marketStore.removeAll })) @observer class BasketItemsCounter extends Component { render() { const { count, onRemoveAll } = this.props; return ( // ... ); } } export default BasketItemsCounter;
کدهای کامل این قسمت را میتوانید از اینجا دریافت کنید: state-management-with-mobx-part4.zip
برای شروع یک پروژه از نوع Console Application ایجاد کنید و یک ارجاع به Assemblyهای زیر رو در برنامه قرار بدید.
- Microsoft.Practices.ServiceLocation
- Microsoft.Practices.Unity
- Microsoft.Practices.EnterpriseLibrary.Common
اگر Assemblyهای بالا رو در اختیار ندارید میتونید اونها رو از اینجا دانلود کنید. Microsoft Enterprise Library یک کتابخانه تهیه شده توسط شرکت Microsoft است که شامل موارد زیر است و بعد از نصب میتونید در قسمتهای مختلف برنامه از اونها استفاده کنید.
- Enterprise Library Caching Application Block : یک CacheManager قدرتمند در اختیار ما قرار میده که میتونید از اون برای کش کردن دادهها استفاده کنید.
Enterprise Library Exception Handling Application Block : یک کتابخانه مناسب و راحت برای پیاده سازی یک Exception Handler در برنامهها است.
Enterprise Library Loggin Application Block : برای تهیه یک Log Manager در برنامه استفاده میشود.
Enterprise Library Validation Application Block : برای اجرای Validation برای Entityها با استفاده از Attribute میتونید از این قسمت استفاده کنید.
Enterprise Library DataAccess Application Block : یک کتابخانه قدرتمند برای ایجاد یک DataAccess Layer است با Performance بسیار بالا.- Enterprise Library Shared Library: برای استفاده از تمام موارد بالا در پروژه باید این Dll رو هم به پروژه Reference بدید. چون برای همشون مشترک است.
برای اجرای مثال ابتدا کلاس زیر رو به عنوان مدل وارد کنید.
public class Book { public string Title { get; set; } public string ISBN { get; set; } }
حالا باید Repository مربوطه رو تهیه کنید. ایتدا یک Interface به صورت زیر ایجاد کنید.
public interface IBookRepository { List<Book> GetBooks(); }
public class BookRepository : IBookRepository { public List<Book> GetBooks() { List<Book> listOfBooks = new List<Book>(); listOfBooks.AddRange( new Book[] { new Book(){Title="Book1" , ISBN="123"}, new Book(){Title="Book2" , ISBN="456"}, new Book(){Title="Book3" , ISBN="789"}, new Book(){Title="Book4" , ISBN="321"}, new Book(){Title="Book5" , ISBN="654"}, } ); return listOfBooks; } }
در مرحله بعد باید Service مربوطه برای استفاده از این Repository ایجاد کنید. ولی باید Repository رو به Constructor این کلاس Service پاس بدید. اما برای انجام این کار باید از ServiceLocator استفاده کنیم.
public class BookService { public BookService() : this( ServiceLocator.Current.GetInstance<IBookRepository>() ) { } public BookService( IBookRepository bookRepository ) { this.BookRepository = bookRepository; } public IBookRepository BookRepository { get; private set; } public void PrintAllBooks() { Console.WriteLine( "List Of All Books" ); BookRepository.GetBooks().ForEach( ( Book item ) => { Console.WriteLine( item.Title ); } ); } }
متد Print هم تمام کتابهای مربوطه رو برامون چاپ میکنه.
در مرحله آخر باید ServiceLocator رو تنظیم کنید. برای این کار کدهای زیر رو در کلاس Program قرار بدید.
class Program { static void Main( string[] args ) { IUnityContainer unityContainer = new UnityContainer(); unityContainer.RegisterType<IBookRepository, BookRepository>(); ServiceLocator.SetLocatorProvider( () => new UnityServiceLocator( unityContainer ) ); BookService service = new BookService(); service.PrintAllBooks(); Console.ReadLine(); } }
بعد از اجرای برنامه خروجی زیر قابل مشاهده است.
آیا به یادگیری یا ادامهی استفاده از AngularJS خواهید پرداخت؟
Ember هم همینطور. قدرتمند هستش ولی این فریم ورک هم داره مثل آنگولار جون میگیره و مطمئن هستم که جنگ سختی بین این دوتا رو شاهد خواهیم بود.