庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

    JUnit的源码相比于spring和hibernate来说比较简单,但麻雀虽小,五脏俱全,其中用到了比较多的设计模式。很多人已经在网上分享了他们对JUnit源码解读心得,我这篇小文谈不出什么新意,本来不打算写,可最近工作上暂时无事可做,那就写写吧,结合《设计模式》来看看。
    我读的是JUnit3.0的源码,目前JUnit已经发布到4.0版本了,尽管有比较大的改进,但基本的骨架不变,读3.0是为了抓住重点,省去对旁支末节的关注。我们来看看JUnit的核心代码,也就是Junit.framework包,除了4个辅助类(Assert,AssertFailedError,Protectable,TestFailure),剩下的就是我们需要重点关注的了。我先展示一张UML类图:

    我们先不去关注TestDecorator类(此处是Decorator模式,下篇文章再讲),看看Test接口,以及它的两个实现类TestCase和TestSuite。很明显,此处用到了Command模式,为什么要使用这个模式呢?让我们先来看看什么是Command模式。

Command模式

Command模式是行为型模式之一

1.意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
2.适用场景:
1)抽象出待执行的动作以参数化对象,Command模式是回调函数的面向对象版本。回调函数,我想大家都明白,函数在某处注册,然后在稍后的某个时候被调用。
2)可以在不同的时刻指定、排列和执行请求。
3)支持修改日志,当系统崩溃时,这些修改可以被重做一遍。
4)通过Command模式,你可以通过一个公共接口调用所有的事务,并且也易于添加新的事务。


3。UML图:
   

4.效果:
1)命令模式将调用操作的对象与如何实现该操作的对象解耦。
2)将命令当成一个头等对象,它们可以像一般对象那样进行操纵和扩展
3)可以将多个命令复合成一个命令,与Composite模式结合使用
4)增加新的命令很容易,隔离对现有类的影响
5)可以与备忘录模式配合,实现撤销功能。

    在了解了Command模式之后,那我们来看JUnit的源码,Test接口就是命令的抽象接口,而TestCase和TestSuite是具体的命令
//抽象命令接口
package junit.framework;

/**
 * A <em>Test</em> can be run and collect its results.
 *
 * 
@see TestResult
 
*/
public interface Test {

    
/**
     * Counts the number of test cases that will be run by this test.
     
*/
    
public abstract int countTestCases();
    
/**
     * Runs a test and collects its result in a TestResult instance.
     
*/
    
public abstract void run(TestResult result);
}

//具体命令一

public abstract class TestCase extends Assert implements Test {
    
/**
     * the name of the test case
     
*/
    
private final String fName;
    
/**
   

//具体命令二

public class TestSuite implements Test {
     

由此带来的好处:
1.客户无需使用任何条件语句去判断测试的类型,可以用统一的方式调用测试和测试套件,解除了客户与具体测试子类的耦合
2.如果要增加新的TestCase也很容易,实现Test接口即可,不会影响到其他类。
3.很明显,TestSuite是通过组合多个TestCase的复合命令,这里使用到了Composite模式(组合)
4.尽管未实现redo和undo操作,但将来也很容易加入并实现。

    我们上面说到TestSuite组合了多个TestCase,应用到了Composite模式,那什么是Composite模式呢?具体来了解下。

Composite模式

composite模式是对象结构型模式之一。
1.意图:将对象组合成树形结构以表示“部分——整体”的层次结构。使得用户对单个对象和组合结构的使用具有一致性。

2.适用场景:
1)想表示对象的部分-整体层次
2)希望用户能够统一地使用组合结构和单个对象。具体到JUnit源码,我们是希望用户能够统一地方式使用TestCase和TestSuite

3.UML图:

      

图中单个对象就是树叶(Leaf),而组合结构就是Compoiste,它维护了一个Leaf的集合。而Component是一个抽象角色,给出了共有接口和默认行为,也就是JUnit源码中的Test接口。

4.效果:
1)定义了基本对象和组合对象的类层次结构,通过递归可以产生更复杂的组合对象
2)简化了客户代码,客户可以使用一致的方式对待单个对象和组合结构
3)添加新的组件变的很容易。但这个会带来一个问题,你无法限制组件中的组件,只能靠运行时的检查来施加必要的约束条件

    具体到JUnit源码,单个对象就是TestCase,而复合结构就是TestSuite,Test是抽象角色只有一个run方法。TestSuite维护了一个TestCase对象的集合fTests:

     private Vector fTests= new Vector(10); 
      
/**
     * Adds a test to the suite.
     
*/
    
public void addTest(Test test) {
        fTests.addElement(test);
    }
    /**
     * Runs the tests and collects their result in a TestResult.
     
*/
    
public void run(TestResult result) {
        
for (Enumeration e= tests(); e.hasMoreElements(); ) {
              
if (result.shouldStop() )
                  
break;
            Test test
= (Test)e.nextElement();
            test.run(result);
        }
    }

当执行run方法时遍历这个集合,调用里面每个TestCase对象的run()方法,从而执行测试。我们使用的时候仅仅需要把TestCase添加到集合内,然后用一致的方式(run方法)调用他们进行测试。

考虑使用Composite模式之后带来的好处:
1)JUnit可以统一地处理组合结构TestSuite和单个对象TestCase,避免了条件判断,并且可以递归产生更复杂的测试对象
2)很容易增加新的TestCase。


参考资料:《设计模式——可复用面向对象软件的基础》
          《JUnit设计模式分析》 刘兵
          JUnit源码和文档









    

posted @ 2007-04-05 15:10 dennis 阅读(4587) | 评论 (3)编辑 收藏

    org.springframework.core.io包中主要是各样的Resource类,Spring的 Resource 接口是为了提供更强的访问底层资源能力的抽象。具体的用法参考spring reference。这个包的类图非常简单,已经有人画的很清晰了,我直接拿过来,来自:Spring代码赏析:Resource类层次



相当巧妙的地方在于,为何AbstractResource的子类有的override了getFile()而有的没有?这是因为在AbstractResource的getFile()方法设计为抛出异常,如果子类没有重写此方法,说明子类不支持通过绝对路径查找资源的方式,而override的子类则提供自己的实现。这里通过类的层次设计,充分利用继承带来的优点,避免了大量的条件语句。

posted @ 2007-04-05 09:23 dennis 阅读(2676) | 评论 (0)编辑 收藏

    好久没看新的小说了,也就追着看《异人》、《新宋》和《大宋时代周刊》,更新不快,我也没多少时间天天看小说。今天上起点,看封推仍然是一大堆的《XXX到异界》的题材,巨无语。《魔炼》这题目也算普普通通,看了书评,评价还不错。读了7,8章,感觉还真不错,炼金术师的故事,蛮有趣,不过感情的描写上不够合理,把握的并不好,维维安牧师同学就这么爱上了我们的男猪脚。另外,看来作者也是喜欢玩WOW的,不少术语和战斗场面很像游戏里的。WOW已经放弃好久了,资料篇出来倒是很想再去艾泽拉斯大陆重新历险一番,嘿嘿。

posted @ 2007-04-03 19:35 dennis 阅读(519) | 评论 (0)编辑 收藏

二叉查找树(binary search tree)

1)概念:对于树中的每个节点n,其左子节点中保存的所有数值都小于n保存的数值,右子节点保存的数值都大于n保存的数值。

2)二叉查找树可以实现更为优越的查找性能,主要实现方式有数组和链表结构,相比较而言,链表实现更为容易,因为数组实现删除和添加功能需要移动数组元素(如填补删除空位等)


今天下午在打印问题搞定后用C#实现了一下,比java版本比较有趣的使用C#的delegate来代替遍历二叉树时的visit方法,这样一来可以在遍历时对节点进行你所想要的任何操作。我们知道C#的delegate是类型化的函数指针,而C++的函数指针可以模仿动态语言的闭包或者匿名函数。这里也有这样的味道。

代码如下,只实现了整数型的,节点定义:
  public  class BSTIntNode
    {
        
public int value;
        
public BSTIntNode left;
        
public BSTIntNode right;

        
public BSTIntNode(int value, BSTIntNode left, BSTIntNode right)
        {
            
this.value = value;
            
this.left = left;
            
this.right = right;
        }

        
public BSTIntNode(int value)
        {
            
this.value = value;
            
this.left = null;
            
this.right = null;
        }
    }

然后定义一个Delegate,作为遍历时的访问方法:

 public delegate void Visit(BSTIntNode node);

然后就是二叉树的实现,删除算法只实现了复制删除法:

public class BSTIntTree
    {
        
protected BSTIntNode root;
      
        
public Visit visit;

        
public BSTIntTree()
        {
            
this.root = null;
        }

        
private BSTIntNode Search(BSTIntNode node, int el)
        {
            
while (node != null)
            {
                
if (el == node.value)
                    
return node;
                
else if (el < node.value)
                    node 
= node.left;
                
else
                    node 
= node.right;
            }
            
return null;
        }

        
//查找
        public BSTIntNode Search(int el)
        {
            
return Search(root, el);
        }

        
//广度优先遍历,利用队列实现,至上而下,至左而右
        public void BreadthFirst()
        {
            BSTIntNode p 
= root;
            Queue queue 
= new ListQueue();
            
if (p != null)
            {
                queue.Enqueue(p);
                
while (!queue.IsEmpty())
                {
                    p 
= (BSTIntNode)queue.Dequeue();
                    visit(p);
                    
if (p.left != null)
                        queue.Enqueue(p.left);
                    
if (p.right != null)
                        queue.Enqueue(p.right);
                }
            }
        }

        
//深度优先遍历,递归实现线序,中序和后序

        
//先序
        protected void PreOrder(BSTIntNode p)
        {
            
if (p != null)
            {
                visit(p);
                PreOrder(p.left);
                PreOrder(p.right);
            }
        }

        
public void PreOrder()
        {
            PreOrder(root);
        }
        
//中序
        protected void InOrder(BSTIntNode p)
        {
            
if (p != null)
            {
                InOrder(p.left);
                visit(p);
                InOrder(p.right);
            }
        }

        
public void InOrder()
        {
            InOrder(root);
        }

        
//后序
        protected void PostOrder(BSTIntNode p)
        {
            
if (p != null)
            {
                PostOrder(p.left);
                PostOrder(p.right);
                visit(p);
            }
        }

        
public void PostOrder()
        {
            PostOrder(root);
        }

        
//插入节点操作
        public void Insert(int el)
        {
            BSTIntNode p 
= root, prev = null;

            
//查找节点位置
            while (p != null)
            {
                prev 
= p;
                
if (p.value < el)
                    p 
= p.right;
                
else
                    p 
= p.left;
            }

            
if (root == null)  //空树
                root = new BSTIntNode(el);
            
else if (prev.value < el)   //大于节点,插入右子树
                prev.right = new BSTIntNode(el);
            
else
                prev.left 
= new BSTIntNode(el);
        }

        
//复制删除法的实现,归并删除法可能改变树的高度
        public void Delete(int el)
        {
            BSTIntNode node, p 
= root, prev = null;

            
//查找节点位置
            while (p != null&&p.value!=el)
            {
                prev 
= p;
                
if (p.value < el)
                    p 
= p.right;
                
else
                    p 
= p.left;
            }
            node 
= p;
            
if (p != null && p.value == el)
            {
                
if (node.right == null)
                    node 
= node.left;
                
else if (node.left == null)
                    node 
= node.right;
                
else
                {
                    BSTIntNode temp 
= node.left;
                    BSTIntNode previous 
= node;
                    
while (temp.right != null)  //查找左字节数的最右子节点
                    {
                        previous 
= temp;
                        temp 
= temp.right;
                    }
                    node.value 
= temp.value;
                    
if (previous == node)
                        previous.left 
= temp.left;
                    
else
                        previous.right 
= temp.left;
                }
                
if (p == root)
                    root 
= node;
                
else if (prev.left == p)
                    prev.left 
= node;
                
else
                    prev.right 
= node;
            }
            
else if (root != null)
            {
                Console.WriteLine(
"没有找到节点:{0}", el);
            }
            
else
                Console.WriteLine(
"树为空!");
        }

    }

注意,在树中我们维持了一个Visit的delegate,看看使用方法:

 public static void Main(string[] args)
        {
           BSTIntTree tree
=new BSTIntTree();
           
int []num={10,20,6,12,23,15,8};
           
for (int i = 0; i < num.Length; i++)
               tree.Insert(num[i]);
           
//添加遍历处理函数,可以有多个 
           tree.visit += new Visit(printNode);
          
           Console.WriteLine(
"广度优先遍历");
           tree.BreadthFirst();
           Console.WriteLine(
"先序");
           tree.PreOrder();
           Console.WriteLine(
"中序");
           tree.InOrder();
           Console.WriteLine(
"后序");
           tree.PostOrder();

           tree.Delete(
8);
           tree.Delete(
15);
           Console.WriteLine(
"删除后广度优先遍历");
           tree.BreadthFirst();

        }
        
public static void printNode(BSTIntNode node)
        {
            Console.WriteLine(
"访问节点:{0}", node.value);
        }

可以看到,C#的delegate机制非常有趣,如果在java中恐怕需要用inner class来实现了。


posted @ 2007-04-02 17:29 dennis 阅读(1596) | 评论 (1)编辑 收藏

    这个星期我的任务就是处理一些报表的打印问题,因为我算项目组里对jasperreport比较熟悉的了,这个东东也是我引进到这个项目。ireport画报表,使用struts的action输出PDF到浏览器,这是我们目前的解决方案。今天遇到一个ireport解决不了的要求——合并单元格。类似下面这样的表结构:
----------------------------------------------
          |          |__c_____________
   dept   | value    |__b_____________
          |          |  a
--------------------------------------------------------
也就是需要跨行,跨列!-_-。在html表格中解决这个很简单,只要设置单元格的colspan和rowspan即可。我在ireport没有找到解决方案,如果您知道,请不吝赐教。查找资料弄了两个小时没进展,决定自己用iText写吧,通过google、baidu资料顺利达到了我的要求,仅在此记录下遇到的问题和解决方法。

一。一个HelloWorld实例:
package com.lowagie.examples.general;

  
import java.io.FileOutputStream;
  
import java.io.IOException;

  
import com.lowagie.text.*;
  
import com.lowagie.text.pdf.PdfWriter;

  
/**
   * Generates a simple 'Hello World' PDF file.
   *
   * 
@author blowagie
   
*/

  
public class HelloWorld {

    
/**
     * Generates a PDF file with the text 'Hello World'
     *
     * 
@param args no arguments needed here
     
*/
    
public static void main(String[] args) {

      System.out.println(
"Hello World");

      
// step a: creation of a document-object
      Document document = new Document();
      
try {
        
// step b:
        
// we create a writer that listens to the document
        
// and directs a PDF-stream to a file
        PdfWriter.getInstance(document,new FileOutputStream("HelloWorld.pdf"));

        
// step c: we open the document
        document.open();
        
// step d: we add a paragraph to the document
        document.add(new Paragraph("Hello World"));
      } 
catch (DocumentException de) {
        System.err.println(de.getMessage());
      } 
catch (IOException ioe) {
        System.err.println(ioe.getMessage());
      }

      
// step e: we close the document
        document.close();
      }
    }
可以看到一个PDF文件的输出,总共只需要5个步骤
a.创建一个Document实例
  Document document = new Document();
b.将Document实例和文件输出流用PdfWriter类绑定在一起
  PdfWriter.getInstance(document,new FileOutputStream("HelloWorld.pdf"));
c.打开文档
  document.open();
d.在文档中添加文字
  document.add(new Paragraph("Hello World"));
e.关闭文档
  document.close();
这样5个步骤,就可以生成一个PDF文档了。

二。如何使用jsp、servlet输出iText生成的pdf?
  如果每次都在服务端生成一个PDF文件给用户,不仅麻烦,而且浪费服务器资源,最好的方法就是以二进制流的形式输送到客户端。
1)JSP输出:
<%@ page import="java.io.*,java.awt.Color,com.lowagie.text.*,com.lowagie.text.pdf.*"%>

<%
response.setContentType
"application/pdf" );
Document document 
= new Document();
ByteArrayOutputStream buffer
= new ByteArrayOutputStream();
PdfWriter writer
=
PdfWriter.getInstance( document, buffer );
document.open();
document.add(
new Paragraph("Hello World"));
document.close();
DataOutput output 
=
new DataOutputStream
( response.getOutputStream() );
byte[] bytes = buffer.toByteArray();
response.setContentLength(bytes.length);
forint i = 0;
< bytes.length;
i
++ )
{
output.writeByte( bytes[i] );
}
%>

2)servlet输出,稍微改造下就可以使用在struts中:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
public void doGet
(HttpServletRequest request,
HttpServletResponse response)
throws IOException,ServletException
{
Document document 
=
new Document(PageSize.A4, 36,36,36,36);
ByteArrayOutputStream ba
= new ByteArrayOutputStream();
try
{
PdfWriter writer 
=
PdfWriter.getInstance(document, ba);
document.open();
document.add(
new
Paragraph(
"Hello World"));
}
catch(DocumentException de)
{
de.printStackTrace();
System.err.println
(
"A Document error:" +de.getMessage());
}
document.close();
response.setContentType
(
"application/pdf");
response.setContentLength(ba.size());
ServletOutputStream out
= response.getOutputStream();
ba.writeTo(out);
out.flush();
}


三。如何输出中文?
    首先需要下载iTextAsian.jar包,可以到iText的主站上下,ireport也是需要这个包的。然后定义中文字体:
    private static final Font getChineseFont() {
        Font FontChinese 
= null;
        
try {
            BaseFont bfChinese 
= BaseFont.createFont("STSong-Light",
                    
"UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            FontChinese 
= new Font(bfChinese, 12, Font.NORMAL);
        } 
catch (DocumentException de) {
            System.err.println(de.getMessage());
        } 
catch (IOException ioe) {
            System.err.println(ioe.getMessage());
        }
        
return FontChinese;
    }

我将产生中文字体封装在方法内,自定义一个段落PDFParagraph继承自Paragraph,默认使用中文字体:
class PDFParagraph extends Paragraph {
        
public PDFParagraph(String content) {
            
super(content, getChineseFont());
        }
    }

使用的时候就可以简化了:

Paragraph par = new PDFParagraph("你好");

四。如何设置PDF横向显示和打印?

Rectangle rectPageSize = new Rectangle(PageSize.A4);// 定义A4页面大小
rectPageSize = rectPageSize.rotate();// 加上这句可以实现A4页面的横置
Document doc = new Document(rectPageSize,50,50,50,50);//4个参数,设置了页面的4个边距

五。如何设置跨行和跨列?

使用PdfPTable和PdfPCell 是没办法实现跨行的,只能跨列,要跨行使用com.lowagie.text.Table和com.lowagie.text.Cell类,Cell类有两个方法:setRowspan()和setColspan()。

六。如何设置单元格边界宽度?

Cell类的系列setBorderWidthXXXX()方法,比如setBorderWidthTop(),setBorderWidthRight()等

七。如何设置表头?
希望每一页都有表头,可以通过设置表头来实现。对于PdfPTable类来说,可以这样设置:
PdfPTable table = new PdfPTable(3);
table.setHeaderRows(
2); // 设置了头两行为表格头

而对于om.lowagie.text.Table类,需要在添加完所有表头的单元格后加上一句代码:
table.endHeaders();

八。如何设置列宽?

Table table = new Table(8);
float[] widths = { 0.10f0.15f0.21f0.22f0.08f0.08f0.10f,
                    
0.06f };
table.setWidths(widths);

上面的代码设置了一个有8列的表格,通过一个float数组设置列宽,这里是百分比。

九。单元格内段落文字居中和换行?
居中通过Cell类来设置,一开始我以为设置段落对齐就可以了,没想到是需要设置单元格:

cell.setHorizontalAlignment(Element.ALIGN_CENTER);


转义符\n实现。在我的这个应用中,因为数据库取出的数据是为了显示在html上的,所以有很多<br>标签,可以使用正则表达式替换成"\n"
"<br>1.测试<br>2.测试2".replaceAll("<br>|</br>","\n");

十。如何显示页码?
复杂的页码显示和水印添加,需要使用到PdfPageEventHelper、PdfTemplate等辅助类,具体的例子参见iText的文档,如果只是为了简单的显示页数,可以使用下面的代码:
            HeaderFooter footer = new HeaderFooter(new Phrase("页码:",getChineseFont()), true);
            footer.setBorder(Rectangle.NO_BORDER);
            document.setFooter(footer);
            document.open();
你可能注意到了,添加footer需要在document.open之前。

posted @ 2007-04-02 15:52 dennis 阅读(12809) | 评论 (9)编辑 收藏

我们一直强调单元测试的重要性,但是有一个问题可能没有认真去想过,测试是重要的,但是我们测试什么呢?最近重读《单元测试之道》,书中给出了答案:Right-BICEP

1.Right——正确

很显然,如果代码运行的结果与你预期的不符合,那么这段代码肯定是有问题的。需要注意的是,Right并意味着正确,因为正确只是相对你所期望的结果而言,而对于用户需求也许就是错误的。

2.B——边界条件
寻找边界条件是单元测试最有价值的工作之一,因为bug一般出现在边界条件上,你常常需要考虑下面这些边界条件:
1)完全伪造或者不一直的数据进行输入
2)格式错误的数据,比如错误的URL,Email地址
3)空值或者不完整值,比如0,null
4)与常理相去甚远的数据,比如人有10000岁?
5)如果要求传入的是一个不允许重复数据的list,你传入一个有重复数据的看看出现什么情况
6)如果需要传入的有序的集合,你传入一个无序的看看结果
7)不按照次序地执行,比如未登录就尝试操作某功能等
对于边界条件,可以按照CORRECT的顺序去尝试:
Conformance——一致性,值是否和预期的一样
Ordering——顺序性,值是否如预期的那样,有序或者无序
Range——区间性,值是否处于合理的范围内
Reference——引用,值是否引用了代码无法空值的外部资源
Existence——值是否存在,为空?为0?不在集合内?
Cardinatity——基数性,检查你的函数能否正确地计数,不多不少
Time——所有的事件的发生是否按照预期的顺序,性能上满足要求?

3.Inverse——检查反向引用
如果方法导致某个结果,尝试以另一个方法能否返回最初的状态?与原状态是否符合预期?

4。Cross——交叉检查
通过不同的方法检查一个方法产生的结果是否正确,比如用Math.sqrt方法检查自己编写的求平方根的方法是否正确。另外的方式,以一种数量去检查另一种数量,比如图书馆借出的书加上架上的书的总数是固定,可以用借出的书来检查架上的书的数量是否正确。

5.E——强制错误条件的产生

一般我们所能想到的环境因素:
1)内存耗光
2)磁盘用满
3)时钟出问题
4)网络不可用或者有问题
5)系统过载
6)调色板颜色数目有限
7)显示分辨率过高

再比如JDK版本差异,我就为这个问题头痛过:)

6.Performance——性能
每天或者每隔几天运行一下一个粗糙简单的性能测试,能够保证你不会在给用户演示的时候出现尴尬的场面。

尽管书上是讲了这么多测试这个、测试那个,我想真实的项目场景中应该根据需要采取特定的测试策略,比如你总不能对于一个单机应用需要考虑地震震断海底光缆引发的问题。就我自己而言,因为项目组中似乎只有我对JUnit等单元测试工具充满兴趣,有经验的老程序员是自己写一个带Main方法的Test类进行测试,而更多的人根本就不知道单元测试或者知道也不感兴趣,在没有压力的情况下,要求自己考虑这么多的测试内容,难矣。今天试用了下NUnit,感觉比JUnit难用多了,JUnit与Eclipse的结合非常简便。






posted @ 2007-03-31 19:18 dennis 阅读(384) | 评论 (0)编辑 收藏

上国外一站点下载一本PDF文档,没想到:

We're sorry, but access is denied to that document.

This might be because you are accessing this site from a machine in China. Because of a massive amount of robot traffic from Chinese machines, we've had to take the unfortunate step of blocking access from those IPs.

If you feel that access has been denied in error, please contact our support folks.


无言以对。



posted @ 2007-03-30 14:57 dennis 阅读(359) | 评论 (3)编辑 收藏

有了前一篇C#链表的实现,实现栈和队列易如反掌。

栈,利用单向链表实现:
public abstract class AbstractStack
    {
        
public abstract Object Pop();
        
public abstract void Push(Object obj);
        
public abstract bool IsEmpty();
        
public abstract Object Top();
        
public abstract void Clear();
    }

    
public class Stack : AbstractStack
    {
        
private SList list;
        
public Stack()
        {
            list 
= new SList();
        }
        
public override bool IsEmpty()
        {
            
return list.IsEmpty();
        }
        
public override void Push(Object obj)
        {
            list.Push(obj);
        }
        
public override object Pop()
        {
            
return list.Pop();
        }
        
public override object Top()
        {
            
return list.getTail();
        }
        
public override void Clear()
        {
            list.Clear(); 
        }
    }

队列的实现,通过双向链表实现,对于环形数组的实现请参考《数组结构之栈与链表》:
 public interface Queue
    {
        
bool IsEmpty();
        
void Enqueue(Object obj);
        Object Dequeue();
        Object First();
    }

    
public class ListQueue:Queue
    {
        
private LinkedList list;
        
public ListQueue()
        {
            list 
= new LinkedList();
        }

        
public bool IsEmpty()
        {
            
return list.IsEmpty();
        }

        
public void Enqueue(Object obj)
        {
            list.Push(obj);
        }
        
public Object Dequeue()
        {
            
return list.Shift();
        }

        
public Object First()
        {
            
return list.getHead();
        }
    }

posted @ 2007-03-30 09:44 dennis 阅读(2184) | 评论 (1)编辑 收藏

     摘要: C#实现单向和双向链表。  阅读全文

posted @ 2007-03-29 17:02 dennis 阅读(1985) | 评论 (1)编辑 收藏

我不知道我是不是头脑发热,突然对编译原理,特别是ANTLR的使用很感兴趣,转个别人总结的步骤。

1.先利用ANTLR之类的编译器生成工具,做一个小程序(如上面提到的HTML文件转化成纯文本文件的程序),所需知识只是正则表达式的基本知识和生成工具本身的使用方法 这样做的好处是:

1)可以体会到编译原理的实用性,提高学习兴趣

2)入门容易,消除编译原理学习的畏难情绪.

3)获得词法分析器和语法分析器的感性认识,有利于加深对理论的理解.

4)获得编译器自动生成工具(compiler compiler)的使用经验,提高解决实际问题的能力.(实际工作很多都不是手编而是利用工具的)

2.象ANTLR之类的工具是开源(open source)的,可研究其源码,以便必要时自己手编分析程序.

3.回过头来看编译原理教材. 这时大概会发现,很多理论很容易懂,剩下的只有上面说的几个难点,多看几遍,重点突破.

4.结合教材所附源码,进一步加深对教材的理解

我决定充实下这篇文章,今天读了anstlr很多文章,来自庄表伟的anstlr系列学习笔记。感觉编译原理并非我想象中那么困难,实现一个简单的脚本解释引擎对我来说还是完全可以做到的,发现我前段时间花大力气学习正则表达式派上了用场,理解起来事半功倍。我决定买本《编译原理》方面的书籍老老实实读一下。不知道有没有人给我推荐一本?



posted @ 2007-03-29 13:42 dennis 阅读(702) | 评论 (0)编辑 收藏

仅列出标题
共56页: First 上一页 43 44 45 46 47 48 49 50 51 下一页 Last