第15课 用户界面与业务逻辑的分离

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

第15课 用户界面与业务逻辑的分离的相关文章

用户界面与业务逻辑的分离

界面与逻辑基本程序架构一般包含:—用户界面模块(UI) 接受用户输入及呈现数据—业务逻辑模块(Business Logic) 根据用户需求处理数据 用户界面与业务逻辑如何交互? 基本设计原则功能模块之间需要进行解耦核心思想:强内聚,弱耦合-每个模块应该只实现单一的功能-模块内部的子模块只为整体的单一功能而存在-模块之间通过约定好的接口进行交互 QCalculatorUI(接收用户的输入,并呈现最终的结果)和QCalculatorDec(实现计算器的核心算法)这两个类没有什么关系,都依赖于顶层的接

细说业务逻辑(一)

内容提要 ===================前篇===================== 前言 内容提要 1.我把业务逻辑丢了!——找回丢失的业务逻辑 2.细说业务逻辑 2.1.业务逻辑到底是什么 2.2.业务逻辑的组成结构 2.2.1.领域实体(Domain Entity) 2.2.2.业务规则(Business Rules) 2.2.3.完整性约束(Validation) 2.2.4.业务流程及工作流(Business Processes and Workflows) 2.3.业务逻辑

java 业务逻辑理解

细说业务逻辑 2016年10月14日 07:16:28 阅读数:2295 细说业务逻辑   前言 记得几个月前,在一次北京博客园俱乐部的活动上,最后一个环节是话题自由讨论.就是提几个话题,然后大家各自加入感兴趣的话题小组,进行自由讨论.当时金色海洋同学提出了一个话题--"什么是业务逻辑".当时我和大家讨论ASP.NET MVC的相关话题去了,就没能加入"业务逻辑"组的讨论,比较遗憾. 其实,一段时间内,我脑子里对"业务逻辑"的概念也是非常模糊的.

XAF视频教程来啦,已出15课

第一到第七课在这里: http://www.cnblogs.com/foreachlife/p/xafvideo_1_6.html 视频地址:http://i.youku.com/i/UMTI5OTEzMDMwMA==/videos?spm=a2hzp.8244740.0.0 之前还是可以贴视频的,现在居然不行了. XAF入门08需求变更流程 XAF入门09按钮 XAF入门10发送邮件 XAF入门11完整模块 XAF入门12汇总查询 XAF入门13新模块 XAF入门14数据透视表数据准备 XAF

用适配器模式处理复杂的UITableView中cell的业务逻辑

适配器是用来隔离数据源对cell布局影响而使用的,cell只接受适配器的数据,而不会与外部数据源进行交互. 源码: ModelCell.h 与 ModelCell.m // // ModelCell.h // Adapter // // Created by XianMingYou on 15/2/10. // Copyright (c) 2015年 XianMingYou. All rights reserved. // #import <UIKit/UIKit.h> #define NAM

项目架构开发:业务逻辑层之领域驱动失血模型

前边我们构建了个数据访问层,功能虽然简单,但是基本够用了.传送门:项目架构开发:数据访问层 这次我们构建业务逻辑层 业务逻辑是一个项目.产品的核心,也是现实世界某种工作流程在代码层面的体现. 所以,业务逻辑的合理组织构造,或更真实地反映现实业务操作,对项目的成功与否非常重要 现在业界对业务逻辑层的开发,一般会参考Martin Fowler大师提出来的针对业务层开发的四种模式 分别是面向过程的事务脚本.表模块模式,面向对象的活动记录与领域开发模式 我们要做的就是领域驱动开发模式,注意标题中的“失血

在 ASP.NET 中创建数据访问和业务逻辑层(转)

.NET Framework 4 当在 ASP.NET 中处理数据时,可从使用通用软件模式中受益.其中一种模式是将数据访问代码与控制数据访问或提供其他业务规则的业务逻辑代码分开.在此模式中,这两个层均与表示层分离.表示层由网站用户有权查看或更改数据的页面组成. ASP.NET 可通过多种方式提供数据访问.业务逻辑和表示形式之间的分离.例如,数据源模型(包括 LinqDataSource 和 ObjectDataSource 等服务器控件)可将表示层与数据访问代码和业务逻辑分离. 另一种模式是将数

系统架构师-基础到企业应用架构-业务逻辑层

一.上章回顾 上章我们主要讲述了系统设计规范与原则中的具体原则与规范及如何实现满足规范的设计,我们也讲述了通过分离功能点的方式来实现,而在软件开发过程中的具 体实现方式简单的分为面向过程与面向对象的开发方式,而目前更多的是面向对象的开发设计方式.并且我们也讲述了该如何通过设计手段去分析功能点及设计分离 点,应该如何在设计的过程中分析的角度及如何去满足设计规范与原则.首先我们通过下图来回顾下上章要点: 二.摘要 本文将已架构的方式去分析分层结构中的业务层的设计,如何写出来内聚度,高耦合的业务逻辑层

提示框的优化之自定义Toast组件之(二)Toast组件的业务逻辑实现

在java下org.socrates.mydiary.activity下LoginActivity下自定义一个方法showCustomerToast() ? 1 public class LoginActivity extends AppCompatActivity { 2 private void showCustomerToast(final int icon, final String message){ 3 LayoutInflater inflater=getLayoutInflate