Thứ Ba, 31 tháng 3, 2015

Cách gắn ckeditor và ckfinder vào website


(Bài viêt này là viết cho php nhưng khi dùng với mvc thì tương tự (thay chỗ nào php -> aspx:nói chung là thay tương ứng thư mục và tên file))
1. Cách gắn ckeditor:
B1: Bạn download bộ ckeditor về,  link: http://ckeditor.com/download, và để vào website của bạn (ví dụ: folder includes)
B2: Trong file code của bạn, chổ nào bạn muốn thẻ textarea biến thành ckeditor thì, trên đầu file đó (trong phần header) bạn include  file ckeditor.js, như sau:
<script language=”javascript” src=”[đường dẫn đến folder ckeditor]/ckeditor/ckeditor.js” type=”text/javascript”></script>
Giả sử textarea của bạn có id là summary, phía dưới textarea đó bạn cho đoạn code sau vào
<script type=”text/javascript”>CKEDITOR.replace( ‘summary‘); </script>
B3: Xong, bạn nhấn refresh lại trang web để xem kết quả nhé.
B4: Cấu hình cho ckeditor, bạn hãy mở file ckeditor/config.js lên, tìm đến function sau đây
CKEDITOR.editorConfig = function( config )
{
//  chuẩn bị thêm các  dòng cấu hình vào đây
}
để thêm vào các dòng code sau nhé:
+ Nếu muốn đổi skin cho ckeditor:
config.skin=’v2′;  // có thể đổi thành 1 trong 3 giá trị sau: v2, kama, office2003
+ Mặc định thì ckeditor sẽ chèn thẻ p vào trước phần văn bản của ta, nếu bạn muốn loại bỏ thẻ p này thì cho dòng code sau vào:
config.enterMode = CKEDITOR.ENTER_BR;
+ Nếu muốn xóa bớt các nút của ckeditor, bạn cho dòng sau đây vào, rồi xóa đi các nút không cần thiết
config.toolbar_Full =
[
['Source','-','Save','NewPage','Preview','-','Templates'],
['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'],
['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'],
‘/’,
['Bold','Italic','Underline','Strike','-','Subscript','Superscript'],
['NumberedList','BulletedList','-','Outdent','Indent','Blockquote','CreateDiv'],
['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],
['BidiLtr', 'BidiRtl' ],
['Link','Unlink','Anchor'],
['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak','Iframe'],
‘/’,
['Styles','Format','Font','FontSize'],
['TextColor','BGColor'],
['Maximize', 'ShowBlocks','-','About']
];
Các bạn có thể xem thêm các cấu hình khác trong folder _sample của ckeditor
2. Cách gắn ckfinder:
Mặc định bộ ckedior khi ta insert hình ảnh vào thì nó không có nút browser  để chúng ta upload hình từ máy lên server, để giải quyết bài toán này các bạn download bộ ckfinder về và để vào chung folder với ckeditor, link download: http://ckfinder.com/download
Để ckfinder hoạt động ta tiến hành các cấu hình sau:
B1: Mở lại file ckeditor/config.js của ckeditor và thêm vào các dòng cấu hình sau:
config.filebrowserBrowseUrl = ‘http://[tên miền của bạn]/ckfinder/ckfinder.html’;
config.filebrowserImageBrowseUrl = ‘http://[tên miền của bạn]/ckfinder/ckfinder.html?type=Images’;
config.filebrowserFlashBrowseUrl = ‘http://[tên miền của bạn]/ckfinder/ckfinder.html?type=Flash’;
config.filebrowserUploadUrl = ‘http://[tên miền của bạn]/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Files’;
config.filebrowserImageUploadUrl = ‘http://[tên miền của bạn]/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Images’;
config.filebrowserFlashUploadUrl = ‘http://[tên miền của bạn]/ckfinder/core/connector/php/connector.php?command=QuickUpload&type=Flash’;
B2: Mở file ckfinder/config.php lên, và chỉnh các thông số sau:
$baseUrl = ‘/[đường dẫn đến folder upload của bạn]/’;
Chú ý là phải có dấu / ở trước và sau đường dẫn.
Ví dụ: Nếu bạn đã upload lên host, và thư mục upload của bạn năm ở ngoài gốc luôn thì ta chỉnh là:
$baseUrl = ‘/upload/’;
Còn nếu bạn đang chạy trên localhost, và thư mục upload đang nằm ở vị trí c:\wamp\www\vidu\upload thì baseUrl sẽ là:
$baseUrl = ‘/vidu/upload/’
Đến đây  tạm xong cơ bản phần gần ckfinder vào ckeditor, bạn thử insert một tấm hình xem có nút browser chưa nhé.
Nếu khi chạy lên, ckfinder báo lỗi: The file browser is disabled for security reasons. Please contact your system administrator and check the CKFinder configuration file.
Cách khắc phục nhanh như sau: bạn hãy mở lại file ckfinder/config.php, tìm đến function CheckAuthentication(), đóng lại (comment) tất cả những dòng code đang có trong function đó, rồi viết vào đó dòng return true, như  sau:
function CheckAuthentication()
{
// WARNING : DO NOT simply return “true”. By doing so, you are allowing
// “anyone” to upload and list the files in your server. You must implement
// some kind of session validation here. Even something very simple as…
// return isset($_SESSION['IsAuthorized']) && $_SESSION['IsAuthorized'];
// … where $_SESSION['IsAuthorized'] is set to “true” as soon as the
// user logs in your system. To be able to use session variables don’t
// forget to add session_start() at the top of this file.
//return false;
return true;
}
Đây là  hàm ckfinder dùng để kiểm tra xem người dùng có đăng nhập vào hệ thống hay chưa, nếu có rồi thì mới cho người dùng upload, còn không thì sẽ hiển thị ra thông báo lỗi như ở trên.
Nếu trong hàm này,bạn chỉ có một dòng code là return true, thì đồng nghĩa với việc không cần kiểm tra  login.
Giả sử trong website của bạn, để kiểm tra xem người dùng có đăng nhập hay chưa, bạn kiểm tra dựa vào $_SESSION['LOGIN'] == ‘DONE’ thì ta có thể chỉnh lại hàm trên như sau
function CheckAuthentication()
{
if ($_SESSION['LOGIN'] == ‘DONE’) return true;
else return false;
//hoặc bạn cũng có thể viết nhanh là: return ($_SESSION['LOGIN']==’DONE’);
}
Read More »

Sử dụng ckeditor


Peter Keating

Developer from the New Forest in the South of England.

Using CKEditor with Razor for .NET MVC 3


At work we have a project coming up that requires content to be constructed by the user. For content entry we will implement a WYSIWYG editor to give users a familiar set of tools to interact with. There are a plethora of WYSIWYG editors available on the web, our chosen one is CKEditor. Having had previous experience with its predecessor the FCKEditor, the CKEditor offers a rich tool set that can be tailored to meet the requirements for content entry. CKEditor comes with well written documentation on a well put together website which is a huge selling point.

Getting Started

To get started, and throw in a quick plug, I am going to use .NET MVC Boilerplate, our open source project at Moov2 to give us a head start with a new project using the .NET MVC framework. We have our project, the next step is to download the CKEditor. The current version at the time of writing is 3.6. Once downloaded, unzip the contents (should be a single directory called "ckeditor") and copy to the root directory in the website.

Implementation

Lets just get the CKEditor loaded on a page in the website. Firstly the JS files for CKEditor need to be included on the web page, include the required JS files into the master page (or just onto the web page the ckeditor is required on) of the project for the moment as shown below.
<script src="ckeditor/ckeditor.js"></script>
<script src="ckeditor/adapters/jquery.js"></script>
ckeditor.js is the main JS file that holds all the code that makes up the CKEditor. I always utilise the JQuery framework to assist with JS coding on a website as it just makes things a lot easier. So in order to get the CKEditor working with JQuery the jquery.js adapter is required. In order to get the editor working, some JS is required in order to convert a DOM element into the CKEditor. You can use a div, p or textarea, I recommend using a textarea because the textarea is a reasonable fallback when the browser doesn't have JS enabled.
<textarea id="my-textarea"></textarea>
Above is the textarea I have added to the web page so it can be converted into a ckeditor. I never use inline JS, so I have added the code below to the main external JS file (js/mylibs/boilerplate.js). The code is basically saying when the document had loaded, convert the specified selector into a ckeditor. If the selector doesn't match any elements in the DOM, then nothing happens.
$(function () {
    $('#my-textarea').ckeditor();
});
Provided you have everything in place correctly, you should see something resembling the screenshot below. The textarea has been converted into a CKEditor offering a rich set of content formatting tools. Super!
Installation.

Sending Content to the Server

So we have our CKEditor on the page, next step is to send the content added by the user to the server so it can be stored or whatever needs to be done with it. With the .NET MVC framework, a process known as model binding is used to send data from the client to a method in a controller on the server. Model binding will take the form values or query string values and try to set those values on the properties of an object instance (the view model), that object is then passed to a method on a controller.
Lets create a view model that will hold the content that is created by the user via the ckeditor.
using System;
namespace CKEditorExample.ViewModels.Content
{
    public class ContentViewModel
    {
        public string Text { get; set; }
    }
}
The Text property will have the user content set on it automatically by MVC 3 before it is passed into a controller method. Next step is to modify the razor view page that shows the ckeditor to tell MVC to bind the textarea to the view model that we just created. Razor provides lots of useful helper methods, particular to do with setting up HTML form elements for model binding. The code for the razor view for the content submission is shown below.
@model CKEditorExample.ViewModels.Content.ContentViewModel

@{
    ViewBag.Title = "Content Entry";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Content Input</h2>

<form method="post" action="/Content/Submit">
@Html.TextAreaFor(x => x.Text)

<p><input type="submit" value="Submit" /></p>
</form>
At the top on the view the model is declared, so now the textarea can be binded to a property on our model. As you can see we are using the razor Html.TextAreaFor to bind a textarea to the Text property on our view model, so when the form is submitted the value in the textarea at the time of submission should be set on the Text property. You may notice that the form is being submitted to /Content/Submit, I have created another razor view / controller method to handle the form submission and display a web page showing the content from the ckeditor that was sent to the server. The ContentController has two methods now, Index which returns a view that displays the form, and Submit that takes care of the form submission.
using System;
using System.Web.Mvc;
using CKEditorExample.ViewModels.Content;

namespace CKEditorExample.Controllers
{
    public class ContentController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        [ValidateInput(false)]
        public ActionResult Submit(ContentViewModel model)
        {
            ViewBag.Text = model.Text;
            return View();
        }
    }
}
Above the Submit method shown in the controller above are a couple of attributes. HttpPost stipulates that only POST requests are accepted. The second attribute ValidateInput is telling MVC not to validate the user input, without this when submitting the form you will see the error message shown below.
Form submission exception when controller method doesn't have ValidateInput attribute.
"[HttpRequestValidationException (0x80004005): A potentially dangerous Request.Form value was detected from the client..." exception is thrown because there is HTML being sent to the server. The [ValidateInput(false)] attribute will prevent this error happening, however we are opening ourselves up to a security vulnerability. So, to handle this you can encode the HTML before it is saved a database, or run checks on the content to ensure there isn't any malicious HTML, such as script tags for JS code blocks. This will allow all our properties on the model to have HTML passed to the server, if your not keen for that you can add [AllowHtml] above the property that is permitted to accept HTML, thus removing the need for the [ValidateInput(false)] attribute.
So submitting the form in the state as shown below.
Content being submitted.
Will result in the web page shown below.
Content after submission.
As you can see the HTML that is created by the CKEditor has been sent to the server and is now displayed in the response. This demonstrates creating a CKEditor and sending the content entered by a user to the server.

Customization

In the screenshots above you can see that CKEditor offers a large set of options in the toolbar. By default CKEditor offers all the features, however you might not want to overwhelm users and offer just a small subset of the commonly used tools. That is easily done, CKEditor comes with a "Full" and "Basic" tool set collection by default. In order to switch to the basic option you need to open config.js in the ckeditor/ directory and make the changes shown below.
/*
Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

CKEDITOR.editorConfig = function( config )
{
    // Define changes to default configuration here. For example:
    // config.language = 'fr';
    // config.uiColor = '#AADC6E';
    config.toolbar = 'Basic';
};
Below is a screenshot of the basic toolbar.
Basic toolbar set on CKEditor.
Now the basic toolbar maybe too minimal. Not to worry, you can create custom toolbar collections. In the same config.js file, you can specify custom toolbars and set the config.toolbar to a custom toolbar. For this, I used the CKEditor documentation to see the names of the different individual tool icons, as there is an example of the "Full" toolset. I have made the following custom toolbar set.
/*
Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

CKEDITOR.editorConfig = function( config )
{
    // Define changes to default configuration here. For example:
    // config.language = 'fr';
    // config.uiColor = '#AADC6E';
    config.toolbar = 'Custom';

    config.toolbar_Custom = [
    { name: 'styles', items: ['Styles', 'Format'] },
    { name: 'basicstyles', items: ['Bold', 'Italic', 'Strike', '-', 'RemoveFormat'] },
    { name: 'paragraph', items: ['NumberedList', 'BulletedList', '-', 'Blockquote'] },
    { name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
        { name: 'insert', items: ['Image'] },
        { name: 'tools', items: ['Maximize', '-', 'About'] }
    ];
};
The toolset that I have made follows the CKEditor convention of naming the toolbar "toolbar_Custom". So in order to change the current toolbar to my own, the config.toolbar is set to "Custom". The toolbar that I have specified is shown in action below.
Custom CKEditor toolbar.
The custom toolbar declaration in the JS code is an array of objects that specify the different sections in the toolbar. The objects in the custom toolbar array are made up of a name and a collection of items. The array of items specify the different tools to be available in that section. You can specify that a spacer be used by adding a "-" in the items collection. So in the screenshot of my custom toolbar, you can see that between the "Strike" and "RemoveFormat" options there is a spacer. In the custom toolbar all the sections are lined up horizontally on a single line, if you have more options though you may want to have sections on multiple lines. To do this, add a '/' to the main array as shown below.
/*
Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or http://ckeditor.com/license
*/

CKEDITOR.editorConfig = function( config )
{
    // Define changes to default configuration here. For example:
    // config.language = 'fr';
    // config.uiColor = '#AADC6E';
    config.toolbar = 'Custom';

    config.toolbar_Custom = [
    { name: 'styles', items: ['Styles', 'Format'] },
    { name: 'basicstyles', items: ['Bold', 'Italic', 'Strike', '-', 'RemoveFormat'] },
    { name: 'paragraph', items: ['NumberedList', 'BulletedList', '-', 'Blockquote'] },
        '/',
    { name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
        { name: 'insert', items: ['Image'] },
        { name: 'tools', items: ['Maximize', '-', 'About'] }
    ];
};
The first three sections will be in a line above the last three sections as shown below.
Custom multi-lined toolbar.

Integration with CKFinder

One of the requirements for content management usually involves users being able to upload images from their computer to the server and have the image included in the content. This functionality doesn't come straight out of the box with CKEditor, it requires integration with CKFinder. CKFinder is an AJAX file manager developed by the creators of CKEditor that integrates seamlessly. CKFinder requires interaction with the server and is available for a handful of platforms, the one we are obviously interested in is ASP.NET. To start integrating the CKFinder download the ASP.NET zip, extract and add the ckfinder/ directory into the root of your website.
Now you have all the files it is worth excluding the _source directory from the project (right click directory and then "Exclude from Project") otherwise lots of compilation errors will appear. Add a reference to CKFinder.dll that can be located in the ckfinder/bin/Release directory.
Next step is to tell our CKEditor instances to use the CKFinder, we do this in the JS. In the same JS file that declares the DOM element to become a CKEditor specify the setup of the CKFinder as shown below.
$(function () {
    $('#Text').ckeditor();

    CKFinder.setupCKEditor(null, '/ckfinder/');
});
null is passed to tell CKFinder to apply to all current instances of CKEditor, otherwise a particular instance of CKEditor can be specified when there are multiple editors on a single page but only a particular one requires CKFinder. When clicking the "Image" option an "Upload" tab now appears as shown below.
CKEditor with the Upload tab.
When first trying to upload an image to the server I was alerted a popup saying "The file browser is disabled for security reasons. Please contact your system administrator and check the CKFinder configuration file.". Turns out that the setup wasn't complete. By default the CKFinder is setup to not authorize sending files to the server. Open ckfinder/config.ascx, this is the main configuration file, temporarily change the CheckAuthentication() method to return true instead of false. It is recommended to implement your own authentication logic to fit with the project, but for this example returning true will do. When re-attempting sending a file to the server you should be greeted with the screen below.

You can also now "Browse Server" on the default image tab, this will allow you to select any files that you have already uploaded to the server.
The config.ascx file is where you go when you need to configure CKFinder. Open the file to see what you can configure. The main parts are the authentication, licensing (LicenseName & LicenseKey properties) and ability to specify where the uploaded files are saved on the server (BaseUrl & BaseDirectory properties).

Production Ready

The CKEditor & CKFinder combine to a total size of around 10mb. Although this isn't a huge amount of disk space, there isn't any point having unneeded files on the server or in source control. The documentation on the CKEditor website has a handy section that describes what files & directories are required. The first directory I deleted was the samples & source directory in both the ckeditor/ & ckfinder/ directory. There are a handful of files in the root of the CKEditor & CKFinder directory that can be deleted, as described at the bottom of the documentation for minimum setup. Once that is done, there isn't much else that can be removed. If you wanted to you could remove the JS files in the lang directory for the languages that you aren't going to be needing.

Licensing

The CKEditor is open source and free for non-commerical use as described on their license page. A clever tactic by the guys at CKEditor though is to charge for the use of CKFinder. Being able to upload images is a integral part of content management, so without the CKFinder the CKEditor is missing a key part of functionality. It is a brilliant WYSIWYG editor so I believe the $59 (£38) fee to use the CKFinder on a personal website isn't too steep.

Source Files

I have uploaded the project that contains the demo onto github.

Final bits

Turns out that implementing CKEditor with CKFinder isn't much of a stress fest, and with useful documentation it was fairly seamless. While implementing this example I was using the new Visual Studio 2012 RC and thoroughly enjoyed using it. I would definitely recommend switching over and giving it a go.
Feel free to email or tweet me if you have any queries or suggestions.
Read More »

Thứ Sáu, 20 tháng 3, 2015

Visitor pattern

Dude, are you still programming using if...then...else?

, 22 Dec 2005
This article shows a concrete example of the true advantages of using design patterns when implementing software.

Introduction

Do you hear a lot about software design, software architecture, and design patterns? But you hardly see any striking advantage to use them in your projects? Or you think of them like they are unusable academic nonsense? Or you simply don't have the time to cope with them? What a pity! And that is for many reasons.
This article will show you a concrete example of why you definitely should have a closer look at design patterns again and again. Consider that the complexity of software is steadily increasing. So all of us need methods for keeping our code easy readable, highly maintainable, and easily extensible without having to give up the flexibility of modern programming languages. Design patterns are the very basics which provide us exactly this. Unfortunately, most articles describe design patterns without really pointing out their advantages.
This article is intended to change this. It will show you on a concrete example how you can keep your projects easy extensible and maintainable by using a single design pattern. After reading it, you will know, what the visitor pattern is intended for, where and why you should use it, and what advantages it gives to your projects. In short – you will know what the true meaning of such keywords like maintainability, extensibility, and reusability of code is, and how you can easily add these valuable issues to your own projects.

Visitor Pattern

This pattern is a robust and highly scalable way for implementing case distinction in your code. Let us construct some very simple example here. Let us assume that we need to implement a simple insurance software. We have an insurance policy which is related to some person. The policy fee is dependent on the gender of a person. Let us assume that women have an initial fee discount of 20%.
Then we need some business logic for our application. So we might need a fee calculator and some component for printing bills for our policies.
At this place, you may object that we do not need extra components for such a bill printer and a fee calculator. Indeed, we could implement methods, like CalculateFee():void and PrintBill():string in the Policy class. But, always keep in mind that such services often need to be adjusted to the current needs of customers. Take the bill printer for instance. The insurer might say someday, that there is no need to print separate bills for different policies possessed by the same person. So if some person has bought more than one insurance policy, the bill should contain all his policies. So if the functionality for printing bills is located in the Policy class, it might be a mess to implement the new customer’s requirement, because in this case, each Policy object has to know each other Policy object in order to find other policies which are referred to the same Person. But we do not want such smart policies. We want dumb policies and smart services on them. Therefore, separate your business logic from your data thoroughly.

How you can implement it

OK. Now, let’s have a closer look at how we could implement our application. The entities are coded quickly. We only need an abstract Person with properties for the Name and the Address, a Man and a Woman as concrete persons, and a Policy with the property for the Person to which the policy is attached to. For simplicity reasons, let us assume, that both the Name as well as the Address of the Person are simple strings. Our entities are shown in the following picture:
Entities
For our business logic, we need the FeeCalculator and the BillPrinter components (see the following picture for more details):
Business logic
Now, both components will need to do a case distinction in order to determine who the corresponding person to the given policy is. Relying on this determination, they do their job in a slightly different fashion. So, the FeeCalculator will give a 20% discount to the initial fee only if the person in the given policy is a Woman. Classically, such case distinctions are coded using an if…then…else statement. It then looks like this (please consider, that all code snippets in the article are pure pseudocode, but you also can download a source project with the working code and experiment with it as much as you want):
public double CalculateFee(Policy p)
{
    if(p.Person.GetType().Equals(typeof(Man)))
        return p.InitialFee;
    else if(p.Person.GetType().Equals(typeof(Woman)))
        return p.InitialFee * 0.8;
    else
        return 0.0;
}
But also, the BillPrinter needs to do such a case distinction for creating an appropriate letterhead with ‘Dear Ms.’ or ‘Dear Mr.’. It could look like this:
public void PrintBill(Policy p)
{
    String bill = "";
    bill += p.Person.Address.ToString();
    if(p.Person.GetType().Equals(typeof(Man)))
        bill += "Dear Mr " + p.Person.Name;
    else if(p.Person.GetType().Equals(typeof(Woman)))
        bill += "Dear Ms " + p.Person.Name;
    bill += "The fee for your police is: ";
    bill += FeeCalculator.CalculateFee(p).ToString(); 
}
As the next step, you get a new requirement to implement a component which shows us some statistics, like how many policies have been sold to male or female persons. The code here is pretty similar to the code in the previous two components:
public int PoliciesSoldOnFemale(Policy[] policies)
{
    int count = 0;
    foreach(Policy current in policies)
        if(current.Person.GetType().Equals(typeof(Woman)))
            count++;
    return count;
}
Well, this implementation looks pretty straightforward and pretty similar by now. But what about the maintenance and extensibility of such code? Let us check this by adding some new requirements. The calculation of the fee should now offer a 50% discount to under-aged persons regardless of the gender.
Therefore, we need to modify our entities first. This is shown in the following picture:
Modified entities which fit new requirements
Now, we need to adjust our CalculateFee method by adding more else…if branches. But as you might already have noticed, our other components need adjustments as well. So, our printer refuses to print bills for under-aged persons. Our statistics also skip under-aged persons by calculating the numbers of sold policies on males and females. So our business logic is out of date. Even worse – it is producing wrong results (as with the statistics example - as it only counts women and doesn't count girls in its PoliciesSoldOnFemale method). It gets even worse if we have to do boxed case distinctions. Imagine that our fee calculation should also regard the age of the person (for example, the fee for a vehicle insurance is higher for younger persons because of the lack in driving experience and thus higher risk of an accident, while the fee for health insurance should be climbing with a higher age). In this case, we might add some age categories as an enumeration to the Person and make a case distinction with switch…case. But this makes our application even more dependent on the structure of our entities.
Now, imagine that your application has hundreds of business logic classes and you programmed only 10 of them by yourself. So you do not know what classes might need an adjustment after modifying your entity classes. The biggest problem is that the compiler still accepts the new code as a valid application. You only have a chance to notice such errors at runtime. In other words, you have to do extended and detailed testing every time you modify your entities before you can say your application is working well.

How you should implement it

As you have seen, making case distinction by if…then…else or by switch…case is error prone. How can we avoid this? Well, surprise, surprise, by using a Visitor pattern as it is described by GOF (Gang Of Four - Design Patterns). The main idea behind this pattern is that, in our case, our business logic has to work with an abstract person and often needs to distinguish which concrete person it is. But the concrete person object alone knows if it is a man or a woman object. So our printer service can ask the person object: “Hey, look, I provide you method X for printing a bill if you are a man and method Y if you are a woman. So tell me who you are and which of these methods I have to use to print a bill for you”. Here is the code for the new bill printing service which can print bills for men and women. First, the adjusted entities:
public abstract class Person
{
    public string Name;
    public string Address;
    public abstract void AcceptPersonVisitor(IPersonVisitor visitor);
}

public class Man : Person
{
    public override void AcceptPersonVisitor(IPersonVisitor visitor)
    {
        visitor.HandleMan(this);
    }
}

public class Woman : Person
{
    public override void AcceptPersonVisitor(IPersonVisitor visitor)
    {
        visitor.HandleWoman(this);
    }
}
    
// this visitor must be implemented by each service, which wants to do
// something with some person and has to make a case distinction
public interface IPersonVisitor
{
    void HandleMan(Man visitee);
    void HandleWoman(Woman visitee);
}
And finally, the new printing service itself:
public class BillPrinter
{
    public void PrintBill(Policy p)
    {
        // create new visitor for printing the bills
        BillPrinterVisitor visitor = new BillPrinterVisitor(p);
        
        // and then ask the person to call the appropriate method 
        // of the visitor
        p.Person.AcceptPersonVisitor(visitor);
    }
}

public class BillPrinterVisitor: IPersonVisitor
{
    private Policy p;
    private string bill = "";

    public BillPrinterVisitor (Policy p){
        this.p = p;
        bill += p.Person.Address.ToString();
    }

    // prints the bill if the visited person is a man
    public void HandleMan(Man visitee)
    {
        this.bill += "Dear Mr. " + visitee.Name;
        this.PrintRest();
    }

    // prints the bill if the visited person is a woman
    public void HandleWoman(Woman visitee)
    {
        this.bill += "Dear Ms. " + visitee.Name;
        this.PrintRest();
    }

    // adds the value of policy fee to the bill regardless
    // if the policy is attached to a man or a woman
    private void PrintRest()
    {
        bill += "The fee for your police is: ";
        bill += FeeCalculator.CalculateFee(this.p).ToString(); 
    }
}
As we can see, the BillPrinterVisitor provides a method HandleMan() for printing a bill if the person is a man, and a method HandleWoman() for printing a bill for a woman. And in the method PrintBill(), the person object is being asked: “Tell me who you are and execute the right method for you” by calling the AcceptPersonVisitor() method. Now, look at the implementation of this method in the Man and Woman classes. You will notice, that if p.Person is a woman, the HandleWoman() method of the visitor is called. Otherwise, if p.Person is a man, the HandleMan() method is called.
The true advantage of the visitor patter is the following. Let us again modify our entities as it is shown in the last picture (by distinguishing between under-aged and full-aged persons). Now, besides the abstract Person class, we have the following new classes:
public abstract class Fullage : Person{}
public abstract class Underage : Person{}

public class Boy : Underage
{
    public override void AcceptPersonVisitor(IPersonVisitor visitor)
    {
        visitor.HandleBoy(this);
    }
}

public class Girl : Underage
{
    public override void AcceptPersonVisitor(IPersonVisitor visitor)
    {
        visitor.HandleGirl(this);
    }
}
Oops, but we do not have methods HandleGirl() and HandleBoy() in our visitor interface. So you can already see at compile time, that there is something wrong here. The project wouldn’t even compile if we didn’t add these methods to our visitor. Let us do it. Oops, the project still can not be compiled. That is because all implementing visitors, like BillPrinterVisitor in our case, do not implement the new added methods. Now, imagine, that all your business logic components, such as FeeCalculator and Statistics in our case, are implemented using visitors. Now, we have a complete overview of which business logic classes might be delivering wrong results and thus need to be adjusted. The compiler says it to us. Isn't it just so damn clever?
Now, we can go ahead and implement a visitor for age categories. Therefore, let us come back again to the requirement to implement a fee calculator for a car insurance policy. As you can recall, the requirement was, that the fee for young persons between 18 and 25 should be higher, due to lack in driving experience. To do just this, we first must add AgeCategory to the Person class. Consider, that concrete classes do not need any modification, because of the inheritance.
public abstract class Person
{
    public string Name;
    public string Address;
    public AgeCategory Age;
    public abstract void AcceptPersonVisitor(IPersonVisitor visitor);
}
Now, we must add some age categories:
public abstract class AgeCategory
{
    public abstract void AcceptAgeCategoryVisitor(IAgeCategoryVisitor visitor);
}
// i.e. for persons, which are between 0 and 17 years old
public class Child : AgeCategory
{
    public override void AcceptAgeCategoryVisitor(IAgeCategoryVisitor visitor){
        visitor.HandleChild(this);
    }
}
// i.e for persons, which are between 18 and 25 years old
public class YoungAged : AgeCategory
{
    public override void AcceptAgeCategoryVisitor(IAgeCategoryVisitor visitor){
        visitor.HandleYoungAged(this);
    }
}
// i.e for persons, which are between 26 and 60 years old
public class MatureAged : AgeCategory
{
    public override void AcceptAgeCategoryVisitor(IAgeCategoryVisitor visitor){
        visitor.HandleMatureAged(this);
    }
}
// i.e for persons, which are between 61 and more years old
public class ElderAged : AgeCategory
{
    public override void AcceptAgeCategoryVisitor(IAgeCategoryVisitor visitor){
        visitor.HandleElderAged(this);
    }
}
Now, we add the visitor interface for age categories:
public interface IAgeCategoriesVisitor
{
    void HandleChild(Child visitee);
    void HandleYoungAged(YoungAged visitee);
    void HandleMatureAged(MatureAged visitee);
    void HandleElderAged(ElderAged visitee);
}
Next, we define an age categories visitor for a car insurance policy:
public class CarInsuranceAgeFeeDiscountVisitor : IAgeCategoriesVisitor {
    private double discount = 0.0;
        
    public double GetDiscount() { return this.discount; }
    
    public void HandleChild(Child visitee){
        throw new ChildrenOughtNotDriveCarsException();
    }

    // young people cause more accidents, thus 50% higher insurance fee
    public void HandleYoungAged(YoungAged visitee){
        this.discount = -.5;
    }

    // people in this age are assumed to be more responsible in traffic 
    // as well, thus grant an additional discount of say 20%
    public void HandleMatureAged(MatureAged visitee){
        this.discount = .2;
    }

    // elder people again cause more accidents due to lower reactivity, 
    // thus no discount
    public void HandleElderAged(ElderAged visitee){
        this.discount = 0.0;
    }
}
Then, we define a visitor for the Person to which the policy is referred.
public class PersonDependantFeeCalculator : IPersonVisitor
{
    public double InitialFee;
    ...
    public void HandleWoman(Woman visitee)
    {
        // we first look, what age the woman has and which age discount
        // is to be granted
        CarInsuranceAgeFeeDiscountVisitor visitor = new   
            CarInsuranceAgeFeeDiscountVisitor();
        visitee.Age.AcceptAgeCategoryVisitor(visitor);
        
        // then we calculate the overall discount
        this.InitialFee = this.InitialFee * (0.8 – visitor.GetDiscount());
    }
}
And last, we define the fee calculator, which calculates the fee dependant on the gender and the age of the person determined by using visitors.
public class FeeCalculator : IPersonVisitor{

    public double CalculateFee(Policy p)
    {
        PersonDependantFeeCalculator visitor = 
            new PersonDependantFeeCalculator();
        p.Person.AcceptPersonVisitor(visitor);
        return visitor.InitialFee;
    }
}
As you can see, you don’t have any boxed case distinctions anymore. In my experience, I have seen up to six times boxed if...then...else branches with hundreds of lines of code per single method. Dude, I programmed this way myself some time ago. And I claim that even those people who programmed such code cannot completely overview the control flow in this code any more in six months time.

Conclusion

So my suggestion is: use Visitors whenever you can. Using the Visitor pattern, you have:
  • Short methods, usually with as much as 5-10 lines of code.
  • Using strong names for your visitors and handling methods elevate the overview of the control flow in your code to a completely new level (you see what happens on the names of your visitors).
  • You have easily extensible and maintainable code.
  • You can add functionality to your entities without having to modify and recompile them. Take different kinds of connections for instance, such as over WLAN, RAS, or the usual 100 Mbit network device. You can realize the Connect() method as a visitor. You, therefore, implement the code for establishing the connection in the handle methods, such as HandleWLAN(Wlan visitee):void, HandleRAS(Ras visitee):void etc. You then might establish the connection like this:
    rasConnection.AcceptConnectionVisitor(new ConnectionEstablisherVisitor());
    Consider, that you don’t need to modify the WLAN or RAS classes. So you don’t need to recompile them. You implement the behaviour for connection in the visitor.
  • You have truly reusable visitors. Take a look at the handling of key events in your GUI for instance. Imagine that you are designing an application for mobile devices. So you might need tens of different dialogues. But you want the reaction on Enter and Esc key be the same in each dialogue. So you must either code this behaviour in each dialogue (i.e., in a switch…case statement; regard the code duplication here). Now, imagine that you have to change the behaviour on pressing the Enter key. Therefore, you must change the code in every single dialogue, or you can define entities for Enter and Esc keys and a visitor on these entities, which implements the behaviour, and call it from each dialogue in a single line before processing other keys. In this case, you only need to modify the visitor in order to change the behaviour.
So, again, use the Visitor pattern whenever you can. Use if…then…else only if you are 100% sure that the same case distinction won’t appear somewhere else in the code (i.e., you might use switch if you are sure of that, i.e., F5-key should only be processed in the current dialogue, or the behaviour on pressing the F1-key is not always the same and differs from dialogue to dialogue).
Try it out and you will love it. Promised.
Please feel free downloading the source of the project and experimenting with it. At the beginning, it is pretty difficult to understand how entities interact with their visitors. It was a big help for me to debug through such visitor calls and especially to have a look at the call stack to fully understand how this pattern works. But once you get it, you wouldn't want to miss it ever more.
Once you have tried out the Visitor pattern, consider having a closer look at other very powerful design patterns. Especially, take a look at the Observer and Singleton design patterns. There are some very useful articles about these patterns here on the CodeProject as well.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
Read More »