1. 界面与逻辑
(1)用户界面模块(UI):接受用户输入及呈现数据
(2)业务逻辑模块(Business Logic):根据用户需求处理数据
2.用户界面与业务逻辑的交互
2.1 基本设计原则
(1)功能模块之间需要进行解耦
(2)核心思想:强内聚、弱耦合
①每个模块应该只实现单一的功能
②模块内部的子模块只为整体的单一功能而存在
③模块之间通过约定好的接口进行交互
2.2 工程开发中的“接口”
(1)广义:接口是一种契约(协议、语法、格式等)
(2)狭义:
①面向过程:接口是一组预定义的函数原型
②面向对象:接口是纯虚类(C#和Java直接支持接口)
2.3 用户界面与业务逻辑的交互
(1)用户界面依赖接口(而不是具体的业务逻辑类)
(2)由具体业务逻辑类去实现业务接口
(3)模块之间仅通过接口进行关联:(必然存在有的模块会使用接口,有的模块去实现接口)
(4)模块间的关系是单向依赖的
①避免模块间存在循环依赖的情况
②循环依赖是糟糕设计的标准之一
3. 计算器应用程序的整体架构
【编程实验】计算器程序集成测试(完整源码)
//Calculator.pro
###################################################################### # Automatically generated by qmake (3.0) ?? 4? 26 00:21:28 2016 ###################################################################### QT += widgets TEMPLATE = app TARGET = Calculator INCLUDEPATH += . # Input HEADERS += QCalculatorUI.h QCalculatordec.h ICalculator.h QCalculator.h SOURCES += main.cpp QCalculatorUI.cpp QCalculatorDec.cpp QCalculator.cpp
//main.cpp
#include <QApplication> #include "QCalculator.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QCalculator* cal = QCalculator::NewInstance(); int ret = -1; if(cal != NULL) { cal->show(); ret = a.exec(); delete cal; } return ret; }
//QCalculatorUI.h
#ifndef _QCALCULATORUI_H_ #define _QCALCULATORUI_H_ #include <QWidget> #include <QLineEdit> #include <QPushButton> #include "ICalculator.h" class QCalculatorUI : public QWidget { //要自定义信号和槽,必须在最开始的这里添加Q_OBJECT Q_OBJECT private: QLineEdit* m_edit; QPushButton* m_buttons[20]; ICalculator* m_cal;//用户界面类,依赖接口 //二阶构造法:当new一个QLineEdit和一些按钮时可能会失败,所以采用二阶构造 QCalculatorUI(); //第1阶——先隐藏构造函数 bool construct();//第2阶 private slots: //声明槽时得加slots void onButtonClicked(); public: static QCalculatorUI* NewInstance(); void show(); void setCalculator(ICalculator* cal); ICalculator* getCalculator(); ~QCalculatorUI(); }; #endif //_QCALCULATORUI_H_
//QCalculatorUI.cpp
#include "QCalculatorUI.h" #include <QDebug> QCalculatorUI::QCalculatorUI(): QWidget(NULL, Qt::WindowCloseButtonHint) { } bool QCalculatorUI::construct() { bool ret = true; const char* btnText[20] = { "7", "8", "9", "+", "(", "4", "5", "6", "-", ")", "1", "2", "3", "*", "←", "0", ".", "=", "/", "C", }; m_edit = new QLineEdit(this);//le的生命期由父组件来管理 if( m_edit != NULL) { m_edit->move(10, 10); m_edit->resize(240, 30); m_edit->setReadOnly(true); //设置编辑框的只读属性 m_edit->setAlignment(Qt::AlignRight); } else { ret = false; return ret; } for(int i = 0; (i < 4) && ret; i++) { for(int j = 0; (j< 5) && ret; j++) { m_buttons[i * 5 + j] = new QPushButton(this);//按钮的生命期由父组件来管理 if (m_buttons[i * 5 + j] != NULL) { m_buttons[i * 5 + j]->resize(40, 40); m_buttons[i * 5 + j]->move(10 + j * 50, 50 + i * 50); m_buttons[i * 5 + j]->setText(btnText[i * 5 + j]); //消息映射 //1.消息名(信号)要加SIGNAL关键字,消息处理函数(槽):用SLOT关键字 //2.信号和槽的函数签名必须一致,即都是无参的函数,返回值void connect(m_buttons[i*5+j], SIGNAL(clicked()), this, SLOT(onButtonClicked())); } else { ret = false; } } } return ret; } QCalculatorUI* QCalculatorUI::NewInstance() { QCalculatorUI* ret = new QCalculatorUI(); if((ret == NULL) || !ret->construct()) { delete ret;//删除半成品 ret = NULL; } return ret; } void QCalculatorUI::show() { QWidget::show(); setFixedSize(width(), height()); } void QCalculatorUI::onButtonClicked() { //sender是QObject类的,用于表示消息的发送者 QPushButton* btn = (QPushButton*)sender(); QString clickText = btn->text(); if(clickText == "←") { QString text = m_edit->text(); if(text.length() > 0) { text.remove(text.length()-1, 1); m_edit->setText(text); } }else if( clickText == "C") { m_edit->setText(""); }else if( clickText == "=") { if(m_cal != NULL) { m_cal->expression(m_edit->text()); m_edit->setText(m_cal->result()); } } else { m_edit->setText(m_edit->text() + clickText); } } void QCalculatorUI::setCalculator(ICalculator* cal) { m_cal = cal; } ICalculator* QCalculatorUI::getCalculator() { return m_cal; } QCalculatorUI::~QCalculatorUI() { }
//ICalculator.h
#ifndef ICALCULATOR_H #define ICALCULATOR_H #include <QString> //接口类 //1.提供一个接受输入表达式的接口 //2.提供一个输出计算结果的接口 class ICalculator { public: virtual bool expression(const QString& exp) = 0; virtual QString result() = 0; }; #endif // ICALCULATOR_H
//QCalculator.h
#ifndef QCALCULATOR_H #define QCALCULATOR_H #include "QCalculatorUI.h" #include "QCalculatordec.h" class QCalculator { protected: QCalculatorUI* m_ui; QCalculatorDec m_cal; QCalculator(); bool construct(); //二阶构造 public: static QCalculator* NewInstance(); void show(); ~QCalculator(); }; #endif // QCALCULATOR_H
//QCalculator.cpp
#include "QCalculator.h" QCalculator::QCalculator() { } //第二阶构造 bool QCalculator::construct() { m_ui = QCalculatorUI::NewInstance(); if(m_ui != NULL) { m_ui->setCalculator(&m_cal); //实现接口的类与UI类的关联 } return (m_ui != NULL); } QCalculator* QCalculator::NewInstance() { QCalculator* ret = new QCalculator(); if(ret ==NULL || !ret->construct()) { delete ret; //删除半成品 ret = NULL; } return ret; } void QCalculator::show() { m_ui->show(); } QCalculator::~QCalculator() { delete m_ui; }
//QCalculatorDec.h
#ifndef QCALCULATORDEC_H #define QCALCULATORDEC_H #include <QString> #include <QStack> #include <QQueue> #include "ICalculator.h" //具体的业务类逻辑,实现了接口 class QCalculatorDec : public ICalculator { protected: QString m_exp; QString m_result; bool isDigitOrDot(QChar c); bool isSymbol(QChar c); //操作符或左右括号 bool isSign(QChar c); //符号位+或- bool isNumber(QString s); bool isOperator(QString s); bool isLeft(QString s); bool isRight(QString s); int priority(QString s); //操作符的优先级 //识别各个token QQueue<QString> split(const QString& exp); //中缀表达式转换后缀表达式 bool transform(QQueue<QString>& exp, QQueue<QString>& output); //判断括号是否匹配 bool match(QQueue<QString>& exp); //重载函数 QString calculate(QQueue<QString>& exp);//对后缀表达式求值 QString calculate(QString l, QString op, QString r);//根据运算符,对左右操作数取值 public: QCalculatorDec(); ~QCalculatorDec(); bool expression(const QString& exp); //QString expression(); QString result(); }; #endif // QCALCULATORDEC_H
//QCalculatorDec.cpp
#include "QCalculatordec.h" #include <QDebug> QCalculatorDec::QCalculatorDec() { m_exp = ""; m_result = ""; } QCalculatorDec::~QCalculatorDec() { } bool QCalculatorDec::isDigitOrDot(QChar c) { return ((‘0‘ <= c) && (c <= ‘9‘)) || (c == ‘.‘); } bool QCalculatorDec::isSymbol(QChar c) { return isOperator(c) || (c ==‘(‘) || (c == ‘)‘); } bool QCalculatorDec::isSign(QChar c) { return (c == ‘+‘) || (c == ‘-‘); } bool QCalculatorDec::isNumber(QString s) { bool ret = false; s.toDouble(&ret); //将字符串转为数字 return ret; } bool QCalculatorDec::isOperator(QString s) { return (s == "+") || (s == "-") ||(s == "*") ||(s == "/"); } bool QCalculatorDec::isLeft(QString s) { return (s == "("); } bool QCalculatorDec::isRight(QString s) { return (s == ")"); } //操作符的优先级 int QCalculatorDec::priority(QString s) { int ret = 0; //非运算符(含括号)优先级最低,为0 if((s == "+") || (s =="-")) { ret = 1; }else if((s == "*") || (s == "/")) { ret = 2; } return ret; } //对用户输入的表达式进行分析与求解 bool QCalculatorDec::expression(const QString &exp) { bool ret =false; QQueue<QString> spExp = split(exp); QQueue<QString> postExp; //后缀表达式 m_exp = exp; if(transform(spExp, postExp)) { m_result = calculate(postExp);//对后缀表达式进行求值 ret = (m_result != "Error"); } else { m_result = "Error"; } return ret; } QString QCalculatorDec::result() { return m_result; } QQueue<QString> QCalculatorDec::split(const QString& exp) { QQueue<QString> ret; QString num = ""; QString pre = ""; for(int i=0; i<exp.length(); i++) { //数字或小数点 if(isDigitOrDot(exp[i])){ num +=exp[i]; pre = exp[i]; //标识符(操作符、左右括号),其它字符(如空格)会被跳过 }else if(isSymbol(exp[i])){ //遇标识符时,表示读到的己经不是数字了,就将num分离并保存起来 if(!num.isEmpty()) { ret.enqueue(num);//num进队列,保存起来 num.clear(); } //如果当前这个非数字的标识符是+或-,则进一步判断是正负号,还是运算符 //当+或-的前一个有效字符为空、左括号或运算符时,这时他们表示正负号。如+9、(-3、5- -3 if(isSign(exp[i]) && ((pre =="") || (pre == "(") || isOperator(pre))) { num +=exp[i]; } else { //运算符或左右括号等符号进队列 ret.enqueue(exp[i]); } pre = exp[i]; //读完一个字符 } } //读完所有的字符,最后一个数字型的串入队列 if(!num.isEmpty()) { ret.enqueue(num); } return ret; } //中缀表达式转换后缀表达式(利用栈来实现中缀转后缀 bool QCalculatorDec::transform(QQueue<QString>& exp, QQueue<QString>& output) { bool bRet = match(exp); QStack<QString> stack; //利用栈来保存操作符 output.clear(); //依次读入各个token while (bRet && !exp.isEmpty()) { QString e = exp.dequeue(); //出队列 if(isNumber(e)) { output.enqueue(e); //数字,直接输出(到队列) }else if(isOperator(e)) //操作符和括号 { //当前运算符的优先级低于栈顶元素(外面水位低,水出湖) while(!stack.isEmpty() && (priority(e) <= priority(stack.top()))) { output.enqueue(stack.pop()); } //压入当前操作符 stack.push(e); }else if(isLeft(e)) //左括号,直接进栈 { stack.push(e); }else if(isRight(e)) //右括号,弹出栈中元素,直到遇到左括号 { while (!stack.isEmpty() && !isLeft(stack.top())) { output.enqueue(stack.pop()); } if(!stack.isEmpty()) { stack.pop();//弹出左括号(丢弃) } }else { bRet = false; } } //将栈中所有剩余的符号弹出 while (!stack.isEmpty()) { output.enqueue(stack.pop()); } if(!bRet) //如果转换时出错,清空输出内容 { output.clear(); } return bRet; } //判断括号是否匹配 bool QCalculatorDec::match(QQueue<QString>& exp) { bool bRet = true; int len = exp.length(); QStack<QString> stack; for(int i=0; i<len; i++) { if(isLeft(exp[i])) //左括号,则进栈 { stack.push(exp[i]); } else if(isRight(exp[i])) //右括号 { //因左括号必定先于右括号出现,否则是错误的表达式 if(!stack.isEmpty() && isLeft(stack.top())) { //遇到一个右括号,则弹出栈顶的左括号与之匹配。 stack.pop(); } else { //遇到右括号时,匹配不成功的原因: //1.栈己经是空的,表示没有左括号可以匹配了 //2.当栈顶元素不是左括号 bRet = false; break; } } } //只有当栈己经空了,并且所有右括号都找到匹配,才返回匹配成功 return bRet && stack.isEmpty(); } //利用栈对后缀表达式求值 QString QCalculatorDec::calculate(QQueue<QString>& exp) { QString ret = "Error"; QStack<QString> stack; //栈 while(!exp.isEmpty()) { QString e = exp.dequeue();//当前token if(isNumber(e)) { stack.push(e); //数字直接进栈 } else if(isOperator(e)) { QString rp = !stack.isEmpty()? stack.pop() : ""; //右操作数 QString lp = !stack.isEmpty()? stack.pop() : ""; //左操作数 QString result = calculate(lp, e, rp); if(result != "Error") { stack.push(result); } else { break; } } else //非法token(即不是数字或运算符) { break; } } if(exp.isEmpty() &&(stack.size() == 1) && isNumber(stack.top())) { ret = stack.top(); } return ret; } //根据运算符,对左右操作数取值 QString QCalculatorDec::calculate(QString l, QString op, QString r) { QString ret = "Error"; if(isNumber(l) && isNumber(r)) { double lp = l.toDouble(); double rp = r.toDouble(); if(op == "+") { ret.sprintf("%f", lp + rp); } else if(op == "-") { ret.sprintf("%f", lp - rp); } else if(op == "*") { ret.sprintf("%f", lp * rp); } else if(op == "/") { const double P = 0.000000000000001; if((-P < rp) && (rp < P)) //除数不能为0 { ret = "Error"; } else { ret.sprintf("%f", lp / rp); } } else { ret = "Error"; } } return ret; }
4. 小结
(1)模块之间的交互需要通过接口完成
(2)接口是开发模块之间的一种“契约”
(3)模块之间不能出现循环依赖
(4)基本设计原则:强内聚、弱耦合
时间: 2024-10-13 15:59:28