邱俊的空间

Simple is beautiful.
posts - 15, comments - 61, trackbacks - 0, articles - 4
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2009年7月25日

前言

异步事件的通知机制在比较有规模的软件设计中必然会有涉及,比如GUI程序中的事件监听器,应用程序模块之间的通信,以及分布式应用中的消息机制等。如果使用语言原生的对象注册通信,则耦合度不可避免的会增大,也就是说,当时间发生时,A要通知B,则A必须知道B的存在。耦合度的增大在一定程度上必然会影响灵活性。所以,另一种模式就是今天要说的总线模式(BUS Based),即所有的监听器将自己挂在总线上,这些监听器互相之间是无法直接通信的,它们可以向总线上push消息,或者从总线上得到消息,从而实现相互间的通信,当然,这种模式会在性能上有一定的额外开销。

BBMS的主页在google code上:http://code.google.com/p/bbms/

总线机制

bbms的客户端程序通过将自己注册在BUS Server上来等待异步事件,这个过程可以是本地的,也可以是远程的。本地的BUS可以作为GUI框架中的事件分发者(dispatcher).JMS(Java Message Service)提供企业级的软件模块之间的通信机制,可以使得多个不同的应用集成为一个大型的应用。通过使用BBMS的远程接口,同样可以达到这样的效果。

BBMS的API

 

/**
 * 
 * 
@author juntao.qiu
 *
 
*/
public class Test{
    
public static void main(String[] args) throws RemoteException{
        
/*
         * create a notifiable entry, declare that it's care of
         * TIMEOUT, CLOSE, and READY event.
         
*/
        Configuration config 
= new RMIServerConfiguration(null0);
        CommonNotifiableEntry entry1 
= 
            
new CommonNotifiableEntry(config, "client1"
                MessageTypes.MESSAGE_TIMEOUT 
| 
                MessageTypes.MESSAGE_CLOSE 
| 
                MessageTypes.MESSAGE_READY);
        
        
/*
         * create another notifiable entry, declare that it's care of
         * OPEN, CLOSE, and TIMEOUT event.
         
*/
        CommonNotifiableEntry entry2 
= 
            
new CommonNotifiableEntry(config, "client2"
                MessageTypes.MESSAGE_OPEN 
| 
                MessageTypes.MESSAGE_CLOSE 
| 
                MessageTypes.MESSAGE_TIMEOUT);
        
        
// register them to the remote Message BUS to listener events
        entry1.register();
        entry2.register();
        
        
// new a message, of type MESSAGE_OPEN.
        Message msg = new CommonMessage(
                entry1.getId(),
                entry2.getId(),
                MessageTypes.MESSAGE_OPEN,
                
"busying now");
        
        
// deliver it to entry2, which is from entry1
        entry1.post(msg);
        
        
// create a message, of type MESSAGE_CLICKED, the entry2
        
// does not handle this type, it'll not be deliver to entry2
        Message msgCannotBeReceived = new CommonMessage(
                entry1.getId(),
                entry2.getId(),
                MessageTypes.MESSAGE_CLICKED,
                
"cliked evnet");
        entry1.post(msgCannotBeReceived);
        
        
try {
            Thread.sleep(
2000);
        } 
catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        
// re use the message object to send another message entry
        msg.setSource(entry2.getId());
        msg.setTarget(entry1.getId());
        msg.setType(MessageTypes.MESSAGE_READY);
        msg.setBody(
"okay now");
        entry2.post(msg);
        
        
// unregister self when all works are done or 
        
// don't want to listen any more
        entry1.unregister();
        entry2.unregister();
    }
}
API的设计,最好可以做到简单,易用。BBMS也尽力要做到这一点,每一个notifiable(可别通知的)的对象,可以将自己注册到BUS上,当消息抵达时,BUS管理器会调用这个对象上的update方法,进行通知。
This is client2, get message from : client1, it said that : busying now
This is client1, get message from : client2, it said that : okay now

这个是MS运行的一个简单流程图。

BUS的实现

BUS接口的定义,可以向BUS上注册一个notifiableEntry(可被通知的对象),或者卸载这个对象,同时,可以向BUS中post一条消息。

package bbms.framework;

/**
 * 
@author juntao.qiu
 
*/
public interface Bus extends java.rmi.Remote{
    
/**
     * mount an notifiable entry on bus
     * 
@param entry
     
*/
    
public void mount(NotifiableEntry entry) throws java.rmi.RemoteException;
    
    
/**
     * unmount the notifiable entry on bus
     * 
@param entry
     
*/
    
public void unmount(NotifiableEntry entry) throws java.rmi.RemoteException;
    
    
/**
     * post a new message to Message Bus
     * 
@param message
     
*/
    
public void post(Message message) throws java.rmi.RemoteException;
}
BUS的实现比较有意思,其中维护两个链表,一个是监听器链表,一个是消息链表,挂载在总线上的实体向BUS发送一条消息,这个过程会立即返回。因为发送消息的过程可能由于网络原因或其他原因而延迟,而消息的发送者没有必要等待消息的传递,所以BUS中有一个主动线程,这个线程在BUS中放入新的消息时被唤醒,并对监听器链表进行遍历,将消息分发出去。由于BUS是一个服务级的程序,所以这个主动线程被设计成为一个daemon线程,除非显式的退出或者出错,否则BUS将会一直运行。
    /**
     * 
     * 
@author juntao.qiu
     * worker thread, dispatch message to appropriate listener
     *
     
*/
    
private class Daemon implements Runnable{
        
private boolean loop = true;
        
public void run(){
            
while(loop){
                
if(messages.size() == 0){
                    
synchronized(messages){
                        
try {messages.wait();} 
                        
catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                processIncomingMessage();
            }
        }
    }
BUS中的内部工作者线程。它被作为一个Daemon线程:
    private MessageBus() throws RemoteException{
        listeners 
= new LinkedList<NotifiableEntry>();
        messages 
= new LinkedList<Message>();
        Daemon daemon 
= new Daemon();
        daemonThread 
= new Thread(daemon);
        daemonThread.setPriority(Thread.NORM_PRIORITY 
+ 3);
        daemonThread.setDaemon(
true);
        daemonThread.start();
        
        
while(!daemonThread.isAlive());
    }

消息的定义

 

public interface Message{
    
public int getType();
    
public void setType(int type);
    
    
public String getTarget();
    
public void setTarget(String target);
    
    
public String getSource();
    
public void setSource(String source);
    
    
public Object getBody();
    
public void setBody(Object body);
}
为了更通用起见,消息体部分可以包含任何对象。消息类型参考了windows的消息机制,可以将消息进行复合:
    /*
     * 0x8000 = 1000 0000 0000 0000
     * 0x4000 = 0100 0000 0000 0000
     * 0x2000 = 0010 0000 0000 0000
     * 0x1000 = 0001 0000 0000 0000
     * 
     * it's very useful when you want to combine some messages
     * together, and the user can simply determine what exactly
     * what you want. Refer the implementation of MessageBus.java
     * for more details.
     
*/
    
public static final int MESSAGE_TIMEOUT = 0x8000;
    
public static final int MESSAGE_CLICKED = 0x4000;
    
public static final int MESSAGE_CLOSE = 0x2000;
    
public static final int MESSAGE_OPEN = 0x1000;
    
    
public static final int MESSAGE_READY = 0x0800;
    
public static final int MESSAGE_BUSY = 0x0400;
    
public static final int MESSAGE_WAIT = 0x0200;
    
public static final int MESSAGE_OKAY = 0x0100;

总结

BBMS如果进行适当的扩展,可以完全实现JMS规范中涉及到的所有主题,如订阅模式(BBMS现在的实现中只有PTP模式,及点对点的模式,发送消息和接受消息的实体都必须同时在线)。BBMS主要面向的是轻量级的消息传递,比如GUI,分布式的GUI等。如果有兴趣,可以到BBMS的页面上看一看:http://code.google.com/p/bbms/

 

posted @ 2009-07-25 22:19 abruzzi 阅读(1941) 评论(4) 编辑

2009年6月21日

前言

事件监听器是经常可以遇到的一种设计模式,一般用在这样一种场景下:当模块的一部分A在完成后需要通知其他的软件模块B,而等待通知的模块B在事先不需要采用轮询的方式来查看另一个模块A是否通知自己。即,当某事件发生,则监听器立刻就知道了该事件。这种模式大量的应用在GUI设计中,比如按钮的点击,状态栏上状态的改变等等。

接口的设计

我们需要一个对事件(event)的抽象,同样需要一个对监听器(listener)的抽象。我们可以把接口抽的很简单:

这个是事件源的接口,只需要提供一个可以获取事件类型的方法即可:

package listenerdemo.framework;

public interface EventSource {
    
public final int EVENT_TIMEOUT = 1;
    
public final int EVENT_OVERFLOW = 2;

    
/**
     * get an integer to identify a special event
     * 
@return
     
*/
    
public int getEventType();
}
 
监听器接口,提供一个当事件发生后的处理方法即可:
package listenerdemo.framework;

/**
 * 
@author juntao.qiu
 
*/
public interface EventListener {
    
/**
     * handle the event when it raise
     * 
@param event
     
*/
    
public void handleEvent(EventSource event);
}
 

事件和监听器框架类图

实例化事件

我们举一个实现了事件源接口(EventSource)的类TimeoutEvent 来说明如何使用事件监听器模型:

 

package listenerdemo;

import listenerdemo.framework.*;

public class TimeOutEvent implements EventSource{
    
private int type;

    
public TimeOutEvent(){
        
this.type = EventSource.EVENT_TIMEOUT;;
    }
    
    
public int getEventType() {
        
return this.type;
    }

}
这个事件的类型为EVENT_TIMEOUT, 当操作超时时触发该事件,我们假设这样一个场景:一个定时器T, 先设置这个定时器的时间为t,当t到时后,则触发一个超时事件,当然,事件是需要监听器来监听才有意义的。我们看看这个定时器的实现:
package listenerdemo;

import listenerdemo.framework.*;

/**
 * 
@author juntao.qiu
 
*/
public class Timer extends Thread{
    
private EventListener listener;
    
private int sleepSeconds;

    
public Timer(int seconds){
        
this.sleepSeconds = seconds;
    }

    
public void setEventListener(EventListener listener){
        
this.listener = listener;
    }
    
    
public void run(){
        
for(int i = sleepSeconds;i>0;i--){
            
try {
                Thread.sleep(
1000);
            } 
catch (InterruptedException ex) {
                System.err.println(ex.getMessage());
            }
        }
        
        raiseTimeoutEvent();//raise一个TimeOut事件给监听器
    }

    
private void raiseTimeoutEvent(){
        
this.listener.handleEvent(new TimeOutEvent());
    }
}

使用事件及其监听器

在类Tester的execute()方法中,我们先设置一个定时器,这个定时器初始化为3秒,设置好定时器后,程序进入一个while(true)循环中,当定时器到时后,它会发送一个timeout事件给当前线程Tester,此时我们可以设置execute中的while条件为false,退出死循环。流程很清晰了,我们来看看代码:

 

package listenerdemo;

import listenerdemo.framework.*;

/**
 * 
@author juntao.qiu
 
*/
public class EventListenerTester implements EventListener{
    
private boolean loop = true;

    
public void execute(){
        Timer timer 
= new Timer(3);//初始化一个定时器
        timer.setEventListener(
this);//设置本类为监听器
        timer.start();
        
        
while(loop){
            
try{
                Thread.sleep(
1000);
                System.out.println(
"still in while(true) loop");
            }
catch(Exception e){
                System.err.println(e.getMessage());
            }
        }

        System.out.println(
"interupted by time out event");
    }


//实现了EventListener接口
    
public void handleEvent(EventSource event) {
        
int eType = event.getEventType();
        
switch(eType){//判断事件类型,我们可以有很多种的事件
            
case EventSource.EVENT_TIMEOUT:
                
this.loop = false;
                
break;
            
case EventSource.EVENT_OVERFLOW:
                
break;
            
default:
                
this.loop = true;
                
break;
        }
    }

    
public static void main(String[] args){
        EventListenerTester tester 
= new EventListenerTester();
        tester.execute();
    }

}

运行结果如下:
run:
still in 
while(true) loop
still in 
while(true) loop
still in 
while(true) loop
interupted by time out event
程序正是按照预期的方式运行了,当然,为了说明主要问题,我们的事件,对事件的处理,监听器的接口都尽可能的保持简单。如果想要完成更复杂的功能,可以参考文章中的方法自行扩充,但是大概流程文中都已经说到。

posted @ 2009-06-21 22:06 abruzzi 阅读(1546) 评论(4) 编辑

2009年6月6日

前言

在学校学程序设计语言的时候,能接触到的所有例子没有一个跟现实世界是有关系的。大多是关注于语言的细节层次,根本没有模型的概念,而我认为,要真正的让别人理解模型是如何建立的,最好的方法是从一个实实在在的东西开始,逐步的建立一个与物理世界可以有对应关系的模型出来。那样,在以后的实践中,可以很轻易的对未知的对象进行数学建模。OO最大的特点并非继承,多态等概念,而是与物理世界建立对应的关系!

选择有限自动机作为例子来说,有这样几点考虑:

  1. 有限自动机几乎是最简单的数学模型,也就是说,它本身就是一个对象
  2. 这个东西是计算理论中的一个比较核心的东西,也很有意思
  3. 有限自动机的形式化定义很明了,很精确,也很简单

当然,文章的主要目的不是要说有限状态机的计算能力,我们要关注的是如何从例子中掌握建模的基本方法。好吧,开始了:

有限自动机

有限自动机是一种抽象出来的机器,其描述能力和资源(存储)都比较有限。其用途十分广泛,特别在机电一体化中有很多地方用到,而有穷自动机和马尔可夫链的结合是当今模式识别的基础(语音识别,光学字符识别等)。

 

有限自动机的形式化定义很简单,是一个5元组(Q, Σ, δ, q0, F),其中

  1. Q是一个有穷集合,称为状态集,定义了自动机所有的状态
  2. Σ是一个有穷集合,称为字母表
  3. δ是一个转移函数,Q×Σ -> Q
  4. q0∈Q 是其实状态
  5. F⊆Q 是接受状态集(可以有多个接受状态s)

也就是说,以上几点唯一的确定一个有限自动机,自动机会有两个最终状态,接受拒绝

建立模型

好,开始建立模型:

  • 首先,我们应该有一个用来描述有限自动机的对象,这个对象有接受输入进行运算得出结论等操作。当然,有限自动机也有很多种,确定型的和非确定型的,只要涉及到很多种具有共性的,我们一般会抽象出共性来做接口
  • 其次,我们可以看到,整个形式化定义都是基于集合的,我们应该有一个用以描述集合的对象,这个对象上可以进行添加元素获取元素删除元素获取集合的大小等操作。
  • 集合中是什么东西呢? 可以看到有状态,有字母表,我们可以考虑设计一个基类(元素),基类上可以拿到元素的真实值。
  • 这个转移函数如何表示? δ实际上是一个矩阵,类似于数字电子中的真值表,因此我们需要有一个对象来表示这个转移函数,这个对象可以根据当前状态和输入字符来查出一个新的状态来(这就是它为什么叫转移函数的原因)。

抽象到这个粒度,可以看出整个系统中的所有对象我们都可以表示了,然后我们可以对这些对象进行适当的简化:

先看看自动机的接口:

package algorithm.machine;

/**
 * 
 * 
@author juntao.qiu
 
*/
public interface StateMachine {
    
/**
     * start the state machine
     
*/
    
public void start();
    
    
/**
     * set the string to evaluate
     * 
@param string the <code>string</code> to be evaluate
     
*/
    
public void input(String string);
    
    
/**
     * check whether the <code>string</code> is accepted by state machine
     * 
@return
     
*/
    
public boolean isAccept();
}

 

集合的接口:

package algorithm.machine;

/**
 *
 * 
@author juntao.qiu
 
*/
public interface Set {
    
/**
     * add a new element into set
     * 
@param element
     
*/
    
public void add(Element element);
    
    
/** 
     * get a element by its index
     * 
@param index
     * 
@return
     
*/
    
public Element get(int index);
    
    
/**
     * get the size of the set
     * 
@return size of the set
     
*/
    
public int size();
    
    
/**
     * check if the set has element <code>e</code>
     * 
@param e
     * 
@return
     
*/
    
public boolean hasElement(Element e);
}

 

集合中元素的接口:

package algorithm.machine;

public interface Element {
    
/**
     * get the real value of an element
     * 
@return
     
*/
    
public String getValue();
}

 

转移函数:

package algorithm.machine;

public interface Matrix {
    
/**
     * get the value while x is an element of State-Set, and
     * an element of Epsilon-Set
     * 
@param x an element of <code>StateSet</code>
     * 
@param y an element of <code>EpsilonSet</code>
     * 
@return
     
*/
    
public Element getElementAt(Element x, Element y);
}

 

接口是最简洁的抽象层次,可以从接口中很清晰的看出整个系统的结构来,所以这里只给出接口的定义,源码可以给出下载链接。

测试

我们来看看Main中的测试,然后就可以知道为什么要这样抽象,这样建模,main中是整个系统运行的脉络,如果接口定义的比较合理,清晰,那么代码读起来会很流畅,希望下面的代码读起来比较流畅,呵呵。

 

    public static void main(String[] args){
        Set stateSet 
= new GeneralSet();//建立状态集
        Set epsilonSet 
= new GeneralSet();//建立符号表
        Set finalSet 
= new GeneralSet();//接受状态集
        
        stateSet.add(
new State("Q0"));
        stateSet.add(
new State("Q1"));
        stateSet.add(
new State("Q2"));
        
        epsilonSet.add(
new State("0"));
        epsilonSet.add(
new State("1"));
        
        finalSet.add(
new State("Q1"));//接受状态为Q1
        
        
/*
         * The transfer matrix
         *     | 0  1
         * ----*--------
         *  Q0 | Q0 Q1
         *  Q1 | Q2 Q1
         *  Q2 | Q1 Q1
         *   
         
*/
        String[][] tran 
= new String[][]{
                {
"Q0""0""Q0"},
                {
"Q0""1""Q1"},
                {
"Q1""0""Q2"},
                {
"Q1""1""Q1"},
                {
"Q2""0""Q1"},
                {
"Q2""1""Q1"}
        };
        
        TransferMatrix matrix 
= new TransferMatrix(tran);//定义转移函数表
        //根据5元组构造一个状态机
        StateMachine machine 
= new FiniteStateMachine(
                stateSet, epsilonSet,matrix,
new State("Q0"),finalSet);
        
        machine.input(
"0100010101011");//在状态机上进行输入
        machine.start();//开始计算
        //判断是否被接受
        
if(machine.isAccept()){
            System.err.println(
"string is accepted");
        }
else{
            System.err.println(
"string is not accepted");
        }
    }

 

 P.S. 本来要插入几张图片的,不知道为什么编辑到一半的时候插入图片老是插不进去,出来的对话框不知道怎么上传本地的图片。

posted @ 2009-06-06 10:48 abruzzi 阅读(1661) 评论(21) 编辑

2009年5月25日

前言

上一篇《C和指针》可能对关于C和指针的有些内容没有说透,下来写了一个链表的实现,当然,也是用C的函数指针来模拟OO的结构来做的。链表结构本身比较复杂(关于指针的使用方面),所以这个例子可能更清晰一些。之所以选List这个例子来说,是因为大家在学校里肯定接触过这个简单数据结构,从一个比较熟悉的例子入手可能比较容易理解一些。

接口定义

可以先看看接口的定义,与Java或者C#类似:

/* 
 * File:   IList.h
 * Author: juntao.qiu
 *
 * Created on May 22, 2009, 2:51 PM
 
*/
 

#ifndef _ILIST_H
#define    _ILIST_H 


typedef 
struct node{
    
void *
data;
    
struct node *
next;
}Node; //定义List中的元素类型,void * 相当于C中的泛型,可以支持任何结构的节点

typedef 
struct
 list{
    
struct list *
_this;
    Node 
*
head;
    
int
 size;
    
void (*insert)(void *
node);
    
void (*drop)(void *
node);
    
void (*
clear)();
    
int (*
getSize)();
    
void* (*get)(int
 index);
    
void (*
print)();
}List; //用head (Node)来维护链表的链!

void insert(void *
node);
void drop(void *
node);
void
 clear();
int
 getSize();
void* get(int
 index);
void
 print(); 

#endif    /* _ILIST_H */ 

 

接口中定义所有的公开的方法,正如所有的List结构一样,我们定义了

void insert(node);//插入一个新的节点到List
void drop(node);//删除一个指定的节点
void clear();//清空List
int getSize();//取到List的大小
void* get(int index);//取到指定位置的元素
void print();//打印整个List,用于调试

 

这样几个方法。

接口的实现

然后看看实现,同上篇一样,引入一个标记链表自身的_this指针,通过对这个指针的引用来修改真实对象中的状态。

 

#include <stdlib.h>
#include 
"IList.h" 

Node 
*node =
 NULL;
List 
*list =
 NULL; 

/* 函数声明块,作用已经在上边解释了*/
void insert(void *node);
void drop(void *
node);
void
 clear();
int
 getSize();
void
 print();
void* get(int
 index); 

/* 构造方法 */
List 
*
ListConstruction(){
    list 
= (List*)malloc(sizeof
(List));
    node 
= (Node*)malloc(sizeof
(Node));
    list
->head =
 node;
    list
->insert =
 insert;
    list
->drop =
 drop;
    list
->clear =
 clear;
    list
->size = 0
;
    list
->getSize =
 getSize;
    list
->get = get
;
    list
->print =
 print;
    list
->_this =
 list; 

    
return (List*
)list;


void
 ListDeconstruction(){

//插入节点,size增加1
void insert(void *
node){
    Node 
*current = (Node*)malloc(sizeof
(Node));
    current
->data =
 node;
    current
->next = list->_this->head->
next;
    list
->_this->head->next =
 current;
    (list
->_this->size)++
;

//删除一个节点,size减1
void drop(void *
node){
    Node 
*= list->_this->
head;
    Node 
*=
 NULL;
    
int i = 0
;
    
for(i;i < list->_this->size;i++
){
        d 
= list->_this->head->
next;
        
if(d->data == ((Node*)node)->
data){
            list
->_this->head->next = d->
next;
            free(d);
            (list
->_this->size)--
;
            
break
;
        }
else
{
            list
->_this->head = list->_this->head->
next;
        }
    }
    list
->_this->head =
 t;

//取到指定index的节点
void* get(int
 index){
    Node 
*node = list->_this->
head;
    
int i = 0


    
if(index > list->_this->
size){
        
return
 NULL;
    }
else
{
        
for(i;i < index;i++
){
            node 
= node->
next;
        }
        
if(node != (Node*)0
){
            
return node->
data;
        }
else
{
            
return
 NULL;
        }
    }


void
 clear(){
    Node 
*node =
 NULL;
    
int i = 0
;
    
for(i;i< list->_this->size;i++
){
        node 
= list->_this->
head;
        list
->_this->head = list->_this->head->
next;
        free(node);
    }
    list
->_this->size = 0
;
    list 
=
 NULL;


int
 getSize(){
    
return list->_this->
size;

//调试用,像这种getSize(), print()这种调用,需要注意的是在调用过程中不能对原始指针做任何修改,
//否则可能出现无法预测的错误。
void
 print(){
    Node 
*node = list->_this->
head;
    
int i = 0
;
    
for(i;i <= list->_this->size;i++
){
        
if(node != (Node*)0
){
            printf(
"[%p] = {%s}\n",&node->data, node->
data);
            node 
= node->
next;
        }
    }
}

 

测试

/* 
 * File:   Main.c
 * Author: juntao.qiu
 *
 * Created on May 21, 2009, 4:05 PM
 
*/
 

#include 
<stdio.h>

#include 
<stdlib.h> 

s#include "IList.h" 

int main(int argc, char** argv) {

    List 
*list = (List*)ListConstruction();//构造一个新的list
    list
->insert("Apple"
);
    list
->insert("Borland"
);
    list
->insert("Cisco"
);
    list
->insert("Dell"
);
    list
->insert("Electrolux"
);
    list
->insert("FireFox"
);
    list
->insert("Google"
);//插入一些节点
    list
->
print();//查看是否插入正确
    printf(
"list size = %d\n",list->
getSize());
   
  //删除两个节点,并打印结果查看。
    Node node;
    node.data 
= "Electrolux"
;
    node.next 
=
 NULL;  
    list
->drop(&
node);
    node.data 
= "Cisco"
;
    node.next 
=
 NULL;
    list
->drop(&
node);

    list
->
print();
    printf(
"list size = %d\n",list->
getSize());
    list
->
clear(); 

    
return 0
;

 

运行结果
$./ooc
[
00489760= {Google}
[
00489730= {FireFox}
[
00489700= {Electrolux}
[004896D0] 
= {Dell}
[004896A0] 
= {Cisco}
[
00489670= {Borland}
[
00489640= {Apple}
list size 
= 7
[
00489760= {Google}
[
00489730= {FireFox}
[004896D0] 
= {Dell}
[
00489670= {Borland}
[
00489640= {Apple}
list size 
= 5

 

可以看出,程序正如预期的那样运行(前一项为节点在内存中的地址,后一项为节点的值),如果大家有兴趣,可以将上一篇《C和指针》s中的Executor装入一个List实现一个Executor的管理器,加入get方法,同时考虑多线程的状态,即可自己完成一个线程池的实现。

 

posted @ 2009-05-25 23:41 abruzzi 阅读(1172) 评论(3) 编辑

2009年5月21日

前言

指针是C的灵魂,正是指针使得C存在了这么多年,而且将长期存在下去。事实上,我自己不用C语言写程序已经有一年了,工作中接触到的只有java,python和javascript。最近用C完成了一下类似于OO中的封装(即"类")的概念,顺便把指针复习了下,感觉有必要记一下。

本文中的例子有这样两个概念:任务(Task),执行器(Executor)。任务有名称(taskName),并且可以执行(execute)。 而执行器与具体任务所执行的内容无关,只是回调(callback)任务的执行方法,这样我们的执行器就可以做的比较通用。而任务接口只需要实现一个execute方法即可,这样我们的任务就可以是多种多样的,可以通过统一的接口set给执行器执行。这是面向对象中基本的思想,也是比较常用的抽象方式。下面我们具体看下例子。

可以想象,main函数大概是这个样子:

 

int main(int argc, char** argv) {

    Task 
*t1 = TaskConstruction("Task1", run);//此处的run是一个函数指针
    Executor 
*exe = ExecutorConstruction();
    exe
->setTask(t1);
    exe
->begin();
    exe
->cancel();

    Task 
*t2 = TaskConstruction("Task2", run2);//此处的run2也是一个函数指针,用于构造一个Task.
    exe
->setTask(t2);
    exe
->begin();
    exe
->cancel();
    
    
return (EXIT_SUCCESS);
}

运行结果为:

task : [Task1] is ready to run
[a 
= 1.200000, b = 2.300000]
[(a 
+ b) * (a - b) = -3.850000]
cancel 
is invoked here
task : [Task2] 
is ready to run
another type of execute,just print 
out some information
cancel 
is invoked here

好了,下面详细看看实现:

定义接口

首先,定义Task和Executor两个实体的接口:

Task接口,注意其中的_this字段,这个指针在后边有很重要的作用,用于hold整个Task的实例。然后是一个taskName的字符串,和一个函数指针,这个指针在初始化(构造)Task时传入。这个execute()函数比较有意思,它不在内部使用,而是让执行器回调执行的。

#ifndef _ITASK_H
#define    _ITASK_H

typedef 
struct Task{
    
struct Task *_this;
    
char *taskName;
    
void (*execute)();
}Task;

void execute();
#endif    /* _ITASK_H */

 

执行器接口比Task接口复杂一些,其中包含_this指针,包含一个对Task的引用,然后是对外的接口begin(), cancel().对接口的使用者来说,他们只需要调用接口实例上的setTask(),将任务传递给执行器,然后在适当时期调用begin(),等待任务正常结束或者调用cancel()将其取消掉。

#include "ITask.h"

#ifndef _IEXECUTOR_H
#define    _IEXECUTOR_H

typedef 
struct Executor{
    
struct Executor *_this;
    Task 
*task;
    
char *(*setTask)(Task* task);
    
void (*begin)();
    
void (*cancel)();
}Executor;

char *setTask(Task *task);
void begin();
void cancel();

#endif /* _IEXECUTOR_H */

 

实现接口

 

#include <stdlib.h>
#include 
"ITask.h"

Task 
*task = NULL;

void execute();

/*
 * The construction of Task object.
 * name : the name of the task
 * execute : execute method of the task
 * 
 
*/
Task 
*TaskConstruction(char *name, void (*execute)()){
    task 
= (Task*)malloc(sizeof(strlen(name))+sizeof(execute));
    task
->taskName = name;
    task
->execute = execute;
    task
->_this = task;
    
    
return (Task*)task;//返回一个自身的指针,通过内部的_this指针,两者即可实现封装
}

/*
 * Destruction of task, not used current time.
 *
 
*/
void TaskDestruction(){
    task
->taskName = NULL;
    task
->execute = NULL;
    task
->_this = NULL;
    task 
= NULL;
}

/*
 * private method, should register to executor
 *
 
*/
void execute(){
    task
->_this->execute();//调用_this上的execute()方法
}

执行器的实现一样,稍微复杂一点,构造的时候,将函数指针在内部设置好,当外部调用时动态的执行需要执行的函数,这句话可能有些绕口,这么看:在构造Executor的时候,executor->begin = begin; 这条语句是将下面void begin()的实现注册到结构体中,但是要执行什么还是不确切的,当setTask以后,回调函数的地址已经明确:(executor->_this->task = task;),此时调用begin()即可正确的调用到注册的Task上。

#include <stdlib.h>
#include 
"IExecutor.h"

Executor 
*executor = NULL;

Executor 
*ExecutorConstruction(){
    executor 
= (Executor*)malloc(sizeof(Executor));
    executor
->begin = begin;
    executor
->cancel = cancel;
    executor
->setTask = setTask;

    executor
->_this = executor;

    
return (Executor*)executor;
}

void ExecutorDestruction(){
    executor
->begin = NULL;
    executor
->cancel = NULL;
    executor
->setTask = NULL;
    executor 
= NULL;
}

char *setTask(Task *task){
    executor
->_this->task = task;
}

void begin(){
    printf(
"task : [%s] is ready to run\n",executor->_this->task->taskName);
    executor
->_this->task->execute();
}

void cancel(){//这个函数没有实现,只是做了一个占位符,以后如果有多线程,可以用来停止主动线程。
    printf(
"cancel is invoked here\n");
}

其实,两个实现的代码都不算复杂,如果对C的指针理解的稍好,基本就没什么问题了。

在C中使用OO

 为了试验,我们不妨设计两个不同的Task,一个Task是计算两个数的某四则混合运算,另一个仅仅是用来打印一点信息。然后我们可以看到,他们使用完全相同的接口来执行:

 

#include <stdio.h>

void run(){//计算(a+b)*(a-b)
    
float a, b, r;
    a 
= 1.2;
    b 
= 2.3;
    r 
= 0.0;
    printf(
"[a = %f, b = %f]\n", a, b);
    printf(
"[(a + b) * (a - b) = %f]\n",((a+b)*(a-b)));
}

void run2(){//打印一句话,事实上,这些函数可以做任何事,比如I/O,网络,图片处理,音乐播放等等。
    printf(
"another type of execute,");
    printf(
"just print out some information\n");
}
然后,在Main中奖他们注册给Task,代码如下所示:
#include <stdio.h>
#include 
<stdlib.h>

#include 
"ITask.h"
#include 
"IExecutor.h"

extern void run();
extern void run2();

int main(int argc, char** argv) {
//代码的风格上,应该可以看出和OO的风格及其类似。
    Task 
*t1 = TaskConstruction("Task1", run);//new Task("Task 1", run);
    Executor 
*exe = ExecutorConstruction();// new Executor();
    exe
->setTask(t1);
    exe
->begin();
    exe
->cancel();
 
    Task 
*t2 = TaskConstruction("Task2", run2);
    exe
->setTask(t2);
    exe
->begin();
    exe
->cancel();
    
    
return (EXIT_SUCCESS);
}
程序的输出结果上文中已经可以看到了,这里就不贴了。
当然,本文的主要目的不是想说什么“C也可以实现面向对象”之类的幼稚观点,只要谁没有严重的自虐倾向,相信不会有谁真的会用C来做OO的开发。只是想表达一下,指针在C中的重要性和指针的一点高级用法。其实现在的OO语言,基本还是以面向过程的表达式来表达面向对象而已。并没有什么神奇之处,OO主要是思想上的抽象,可以说是语言无关的(language independent)。

posted @ 2009-05-21 23:15 abruzzi 阅读(1925) 评论(12) 编辑

2009年4月18日

摘要: 前言Javascript,有人称其为C+LISP,C只怕是尽人皆知,但是一直活跃在人工智能领域的另一个古老而优美的语言LISP,掌握的恐怕不是很多.这个倒不是因为这个语言太难或者用途不广泛,而是大多数人在接受计算机语言启蒙的时候都走的是图灵机模式,而LISP,做为一种函数式编程语言,是另一个体系:lambda演算体系.这个体系的运算能力跟图灵机的运算能力是相当的。所以Javascript本身是一种...阅读全文

posted @ 2009-04-18 22:18 abruzzi 阅读(2470) 评论(13) 编辑

摘要: Hash表这种数据结构在java中是原生的一个集合对象,在实际中用途极广,主要有这么几个特点:访问速度快大小不受限制按键进行索引,没有重复对象用字符串(id:string)检索对象(object)今天整理以前在学校写的一些算法,翻出来一个hash表的实现,就贴出来,自己也温习温习。先看看头文件,也就是数据结构的定义,相当于java中的接口的概念:[代码]然后是具体实现:[代码]很简单,只有两个外部...阅读全文

posted @ 2009-04-18 17:28 abruzzi 阅读(1161) 评论(0) 编辑

摘要: 打算写一个系列,比较系统的介绍一下一个脚本引擎的设计和实现过程,本来打算使用lex/yacc来举例子,但是由于最近对java语言有了新的认识,故决定使用javacc这个工具来做,这个系列中就是以javacc中的一个比较复杂的例子来进行解说的。这篇文章是本系列的第一篇,主要说几个概念的定义,有了这些定义,后边就容易理解了。对一个语言的源文件进行解析,主要是做这样几件事:词法分析语法分析语义分析当然,...阅读全文

posted @ 2009-04-18 14:42 abruzzi 阅读(1716) 评论(3) 编辑

2008年12月30日

摘要: 作者: abruzzi 链接:http://abruzzi.javaeye.com/blog/266335 发表时间: 2008年11月08日 声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!用途及用法网络请求通常有两种形式:第一种,请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等...阅读全文

posted @ 2008-12-30 21:08 abruzzi 阅读(171) 评论(0) 编辑

摘要: 作者: abruzzi 链接:http://abruzzi.javaeye.com/blog/266027 发表时间: 2008年11月12日 声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!事件监听器模式(一个简单的例子)在GUI程序设计中,在设计时通常不会知道用户的动作什么时候会发生,如用户可能点击某一个按钮,也可能不点击,而且点...阅读全文

posted @ 2008-12-30 21:08 abruzzi 阅读(96) 评论(0) 编辑