您的位置:首页 > 其它

通过一个简单控制台的实现来漫谈软件工程基本概念

2013-01-26 16:14 435 查看
之前我曾写过同样内容的文章并发表在我的CSDN博客。我让我的同学过目,但他似乎并没有看懂。然后我就卸下来了。因为我感觉我的文笔确实不够好,很多道理阐述的过于冗杂,让别人无所适从。在此,又重写之,估计已经时隔一年了。

好了闲话不说,首先阐述我写这篇博客的目的吧。我想采用JAVA实现一个简单的控制台来浅谈关于软件工程方面的相关技术和概念。也许这些都是次要的,我的终极目标是想通过一个简单的不能再简单的实例,来一步步地体验软件开发的全过程,并掌握软件开发里的基本交流语言。并且,在此基础上,让那些对自己技术实力没有过多自信的人找到合适的切入点,让他们自己能够真正领悟到:软件开发是个极度包容的过程,几乎任何人都可以参与。他们所要学习的,就是在这个过程中,如何扬长避短,并把自己的技术短板限制在可控的范围内,以求自身真正地融入团队中。

任何软件开发事业,首先需要明确需求。我不想过多的谈论需求的重要性,它可能会占用过多的篇幅。我只想说,在需求明确之前,永远不要进行下一步。你要在动笔写代码之前就要搞清楚你所要实现的东西是什么样子,而不是在开发过程中逐渐熟悉它是什么样子。

下面述说我的需求:

即实现一个控制台。所谓控制台,是指类似于windows系统的cmd的东西。显然它的功能包括:1.能够输出字符串到屏幕上;2.能够接受用户输入从而获取字符串。

在这里我想通过JAVA实现。用什么语言无所谓,关键是阐述的道理都是一致的。java是我最熟悉的语言,我用它已经三年了。

然后等一下,我在这里想要多加一个需求。我想要阐述一下有关组件的概念。我希望我所实现的控制台,它是一个组件,而不仅仅是一个应用程序。所谓组件,就是能够提供一个或一系列相关功能的工具包。在java里,它可能是一个类,也可能是几个类联合起来实现一个功能。总之,它是可以被其他更高层的程序利用的。所以,我在这里添加一个需求,也许与最终的用户无关,但是对于开发人员却是必要的:即作为一个组件,为其他应用程序提供可访问的接口。事实上,软件开发就是某种程度的组件组合。

好吧,总结一下三个需求:

1.能够输出字符串到屏幕上;

2.能够接受用户输入从而获取字符串。

3.作为一个组件,为其他应用程序提供可访问的接口。

明确需求过后,需要进一步细化需求,或者可以称之为大家熟悉的案例。那我就随意细化之了。为了节省篇幅,我就并不写的很详细了。平常大家开发时,可以口头交流细节,但最好还是要文档化。在这里,我的控制台跟大家见过的差不多。就是可以输出字符串,光标总是在最后面,接受字符串一直到按下enter键才有反应等等。事实上这些不重要,我的需求的三个功能已经很明确了,它们只不过是不够详细罢了。详细和明确并不对等。

接下来可以真正考虑去实现了。首先我定义一个接口,称之ConsoleInterface。其具体代码如下:

import javax.swing.JComponent;

public interface ConsoleInterface {

void write(char[] content);		//将字符数组的内容写到Console

char[] read(int num);		//从Console中读取num个字符

JComponent getComponent();		//返回Console的图形化表示

}
这里采用的是接口和实现分离的模式。其实整个设计模式的方法学都是在围绕这个模式进行的。这在当你感到实现时无所适从时尤其有用,也在你预料到你的实现是有局限性,以后可能会被另外的实现替代也是有用的。总之,是对于自己不会做以及预料到以后的维护时是行之必要的。所以,当自己感到自己似乎在完成一个超出自己能力的事情时,不要过分沮丧,先定义一个接口放在那里,日后再来处理也不迟。这就是第一个准则:当自己不能完成任务时,尝试定义一个接口,这样它就总在那里,而不是一个空白。

另外,需要说明的是,这个准则不要过分滥用。亦即,不要遇到什么就尝试去定义一个接口。如果你的功能你能够一气呵成地完成,而且根本不用担心日后的维护时,应该放弃先定义接口的方式。毕竟,接口与实现分离是一种曲线救国的方式,是为了长久性的考虑或者是不得已而为之的结果。这里,我并不打算让自己采用接口的方式,毕竟只是个小项目,而且我对自己完成它是充满信心的。好了,故而我生命一个类,它有个更简洁优雅的名称,如下:

//Console版本1

import javax.swing.JComponent;

public class Console {

//将字符数组的内容写到Console
public void write(char[] content) {

}

//从Console中读取num个字符
public char[] read(int num) {
return null;
}

//返回Console的图形化表示
public JComponent getComponent() {
return null;
}
}
它就是这个样子,没有过多的细节了。方法实现就是一个空白或者return null(如果你愿意,return null也可以不要)。当然,这可以替换成诸如“throw new UnsupportedException()"之类。这就是第二个准则:不要一开始就杀入细节(当然,你很天才就例外),从骨架开始,慢慢理清思路。

这个时候不要一筹莫展,先实现一部分再说。首先,不去考虑有关图形界面的部分,它可能是一个JTextArea,也可能是一个JPanel或者别的是什么,但先不去考虑这么多了,我们只需假设它具有下面的能力即可:

1.如果给它一个char数组,它能够立刻显示其中的内容。

2.如果用户输入一段文字,但是没有按下enter键,它是不能感知的,而且可以修改用户以前键入的部分;但是如果用户按下enter键,这部分输入就被感知并不可修改了,产生了新近输入的文字,并且激发一系列的反应。

3.当然,它本身是一个JComponent,我们可以从中获得。

根据以上的能力,我们就得到了具有以下功能的接口:

import javax.swing.JComponent;

public interface ConsoleComponent {

public void write(char[] content);

public void setInputListener(InputListener l);

public JComponent getComponent();

}


public interface InputListener {

public void processInputData(char[] inputData);

}


这里涉及到一种设计模式即称为监听器模式,或者叫观察者模式。可以参考相关资料进一步理解。这是对应于功能2的具体设计。有些人称之为事件驱动模型,意思是一个组件本身能够产生一系列的事件,而事件本身会激发已经定义的行为。这种行为是可控的,自我定义的。例如海水涨潮了是一个事件,我们可以添加一个行为,例如加高堤坝。也可以定义另一个行为,例如开闸放水。

好了,有了这个工具,就已经无压力的实现我们的功能了。

import java.util.concurrent.BlockingQueue;
import javax.swing.JComponent;

public class Console {

private BlockingQueue<Character> charBuffer  = new LinkedBlockingQueue<>();

private ConsoleComponent comp;

public Console(ConsoleComponent comp) {
this.comp = comp;
comp.setInputListener(new InputListener() {
@Override public void processInputData(char[] inputData) {
try {
for(char c: inputData)
charBuffer.put(c);
charBuffer.put('\n');
} catch(InterruptedException ex) {
throw new RuntimeException(ex);
}
}
});
}

//将字符数组的内容写到Console
public void write(char[] content) {
comp.write(content);
}

//从Console中读取num个字符
public char[] read(int num) {
try {
char[] content = new char[num];
for(int i = 0; i < num; i++) {
content[i] = charBuffer.take();
}
return content;
} catch(InterruptedException ex) {
throw new RuntimeException(ex);
}
}

//返回Console的图形化表示
public JComponent getComponent() {
return comp.getComponent();
}
}


这样就够了,你已经完成了一个控制台的实现细节。现在所差的就是一个图形界面,但是对于这个方面,你也已经在接口定义和实现约束上有了控制。下面要么你可以自己去学习相关的SWING知识,或者请一个别的熟悉这方面知识的人来完成剩下的工作。这里我给一个简单的实现:

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import javax.swing.text.BadLocationException;

public class TextAreaConsoleComponent implements ConsoleComponent {

private JTextArea textArea = new JTextArea();

private int nextStart = textArea.getDocument().getLength();

private InputListener listener;

private List<Character> buffer = new ArrayList<>();

public TextAreaConsoleComponent() {
textArea.addKeyListener(new MyKeyListener());
textArea.setEditable(false);
}

@Override
public void write(char[] content) {
textArea.append(new String(content));
nextStart = textArea.getDocument().getLength();
}

@Override
public void setInputListener(InputListener l) {
listener = l;
}

@Override
public JComponent getComponent() {
return textArea;
}

private class MyKeyListener extends KeyAdapter {

@Override
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
if(!buffer.isEmpty()) {
try {
textArea.getDocument().remove(
textArea.getDocument().getLength() - 1, 1);
} catch (BadLocationException ex) {
throw new RuntimeException(ex);
}
buffer.remove(buffer.size() - 1);
}
} else if(e.getKeyCode() == KeyEvent.VK_ENTER) {
char[] chars = new char[buffer.size()];
for(int i = 0; i < chars.length; i++) {
chars[i] = buffer.get(i);
}
buffer.clear();
textArea.append("\n");
listener.processInputData(chars);
} else {
buffer.add(e.getKeyChar());
textArea.append(String.valueOf(e.getKeyChar()));
}
}

}

}


至此, 将代码copy并编译,可以发现能够使用的。下面给出一个简单的测试。
import javax.swing.JFrame;

public class Test {

public static void main(String[] args) throws Exception {
Console console = new Console(new TextAreaConsoleComponent());
JFrame frame = new JFrame();
frame.add(console.getComponent());
frame.setSize(500, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
console.write("Hello World".toCharArray());
char[] str = console.read(10);
System.out.println(str);
}

}


如此一来,一切就完成了。而我想要阐述的道理不在于这个实现,而是在于无论个人的能力如何,都能够融入团队,做自己力所能及的事情。所以如果面临一个似乎超出自己能力的任务时,先做自己能够做的,并把自己不能做的得到有效的封装。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐