`

从结构化编程到面向对象编程

阅读更多

本文所讨论的话题

通常在一个业务系统中会有各种不同的角色,而在系统的若干功能模块中,这些角色所能看到的数据是不一样的。那么在程序中如何处理类似问题会更优呢,本文想通过一个简单的场景来和大家再次探讨一下如何用OO来改善我们的系统。

 

场景

 系统中目前有三种角色,超级管理员、管理员、普通用户。有一个功能是显示书籍列表,每一种角色所能看到的书籍是不同的。这里面会有一些规则,但是这些规则不是文本所要讨论的重点。

 

最初的实现

通常我们最快能想到的思路是,先创建两个类,用户类User、书籍类Book。在User类中我们创建了判断用户角色类型的方法。

package fangwei.solution1.user.domain;

public class User {

    public static final int ROLE_SUPER_ADMIN = 1;
    public static final int ROLE_ADMIN = 2;
    public static final int ROLE_COMMON_USER = 3;
    
    private Integer roleId;

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }

    public Integer getRoleId() {
        return roleId;
    }
    
    public boolean isSuperAdmin() {
        return this.roleId==ROLE_SUPER_ADMIN;
    } 
    
    public boolean isAdmin() {
        return this.roleId==ROLE_ADMIN;
    } 
    
    public boolean isCommonUser() {
        return this.roleId==ROLE_COMMON_USER;
    } 
    
}

 

package fangwei.solution1.book.domain;

public class Book {

}

 

然后用分层的方式去处理问题,先创建service层的接口。为了处理不同角色的情况,我们传入了一个user对象。

package fangwei.solution1.book.service;

import java.util.List;

import fangwei.solution1.book.domain.Book;
import fangwei.solution1.user.domain.User;

public interface BookService {

    public List<Book> listBook(User user);

}

  

实现service层的接口,同时我们需要创建dao层的接口

package fangwei.solution1.book.service;

import java.util.List;

import fangwei.solution1.book.dao.BookDao;
import fangwei.solution1.book.domain.Book;
import fangwei.solution1.user.domain.User;

public class BookServiceImpl implements BookService {

    private BookDao bookDao;
    
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public List<Book> listBook(User user){
        List<Book> result = null;
        if(user.isSuperAdmin()){
            result = bookDao.selectBookList4SuperAdmin();
        }else if(user.isAdmin()){
            result = bookDao.selectBookList4Admin();
        }else if (user.isCommonUser()) {
            result = bookDao.selectBookList4CommonUser();
        }
        return result;
    }

}

 

package fangwei.solution1.book.dao;

import java.util.List;

import fangwei.solution1.book.domain.Book;

public interface BookDao {
    public List<Book> selectBookList4SuperAdmin();
    
     public List<Book> selectBookList4Admin();
            
     public List<Book> selectBookList4CommonUser();
}

 

实现dao层的接口,为了本文的讨论,我们加入了log语句,而并没有真正去编写实现代码

package fangwei.solution1.book.dao;

import org.apache.log4j.Logger;

import java.util.List;

import fangwei.solution1.book.domain.Book;

public class BookDaoImpl implements BookDao {

    private static final Logger logger = Logger.getLogger(BookDaoImpl.class);
    
    public List<Book> selectBookList4SuperAdmin() {
        logger.debug("selectBookList4SuperAdmin");
        return null;
    }

    public List<Book> selectBookList4Admin() {
        logger.debug("selectBookList4Admin");
        return null;
    }
    
    public List<Book> selectBookList4CommonUser() {
        logger.debug("selectBookList4CommonUser");
        return null;
    }

}

  

好了,下面我们来编写一个测试,来看service层的实现是否正确

package fangwei.solution1.book.service;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import fangwei.solution1.book.dao.BookDaoImpl;
import fangwei.solution1.book.service.BookServiceImpl;
import fangwei.solution1.user.domain.User;

public class TestBookServiceImpl {

    private BookServiceImpl bookService;

    @Before
    public void setUp() throws Exception {
        bookService = new BookServiceImpl();
        bookService.setBookDao(new BookDaoImpl());
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testListBook() {
        User user = new User();
        user.setRoleId(User.ROLE_SUPER_ADMIN);
        bookService.listBook(user);
        
        user.setRoleId(User.ROLE_ADMIN);
        bookService.listBook(user);
        
        user.setRoleId(User.ROLE_COMMON_USER);
        bookService.listBook(user);
    }

}

 

运行这个测试,输出结果如下

selectBookList4SuperAdmin
selectBookList4Admin
selectBookList4CommonUser

 

说明这个实现是正确的。

问题在哪里

在一个业务系统中类似上面的场景会有很多,也就是说我们会在service层的若干实现方法中使用if...else if...else if...用来处理不同角色的情况。如果角色类型是不会增加的,那么上面的实现没有任何问题。但是很可惜,客户在使用系统一段时间后提出要增加新的角色类型,当然新角色所能看到的书籍列表也是有别于之前角色的:(

这个时候,我们痛苦的发现,我们需要满系统去找使用if...else if...else if...用来处理不同角色的地方,然后再加入一个else if。有人会说,这没什么啊,我们的团队是按功能模块划分来开发的,每个人挨个service类去加就好了。但是我当时的想法是,一定有比这更好的设计。

另一种实现

我们再回过头去看service层的listBook方法,其实我们要做的事只有一件——查询书籍列表,只是因为角色这个因素导致出现了分支结构。那么我们可以将这个变化抽象出来,使我们在查询书籍列表时不需要关心角色这个因素。下面是另一种实现的service层接口,同时我们抽象出了新的接口BookBiz

package fangwei.solution2.book.service;

import java.util.List;

import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;

public interface BookService {

    public List<Book> listBook(BookBiz bookBiz);

}

 

package fangwei.solution2.book.domain;

import java.util.List;

import fangwei.solution2.book.domain.Book;

public interface BookBiz {

    public List<Book> listBook();
    
}

 

这样,我们在实现service层接口的时候就不需要考虑角色因素了

package fangwei.solution2.book.service;

import java.util.List;

import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;

public class BookServiceImpl implements BookService {

    public List<Book> listBook(BookBiz bookBiz) {
        return bookBiz.listBook();
    }
    
}

 

 那么我们如何使用BookBiz这个接口呢,我们可以创建三个新的用户类分别对应于场景中三种不同的角色,然后让他们实现BookBiz这个接口。为了测试的简单,我们直接依赖了dao层的实现BookDaoImpl,在实际当中可以使用spring等ioc容器在运行期注入。

 

package fangwei.solution2.user.domain;

import java.util.List;

import fangwei.solution2.book.dao.BookDao;
import fangwei.solution2.book.dao.BookDaoImpl;
import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;

public class SuperAdminUser extends User implements BookBiz {

    BookDao bookDao = new BookDaoImpl();
    
    public List<Book> listBook() {
        return bookDao.selectBookList4SuperAdmin();
    }

}

 

 

package fangwei.solution2.user.domain;

import java.util.List;

import fangwei.solution2.book.dao.BookDao;
import fangwei.solution2.book.dao.BookDaoImpl;
import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;

public class AdminUser extends User implements BookBiz {

    BookDao bookDao = new BookDaoImpl();
    
    public List<Book> listBook() {
        return bookDao.selectBookList4Admin();
    }

}

 

 

package fangwei.solution2.user.domain;

import java.util.List;

import fangwei.solution2.book.dao.BookDao;
import fangwei.solution2.book.dao.BookDaoImpl;
import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;

public class CommonUser extends User implements BookBiz {

    BookDao bookDao = new BookDaoImpl();
    
    public List<Book> listBook() {
        return bookDao.selectBookList4CommonUser();
    }

}

 

 依然需要写一个测试来验证我们的设计

 

package fangwei.solution2.book.service;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import fangwei.solution2.book.domain.BookBiz;
import fangwei.solution2.book.service.BookService;
import fangwei.solution2.book.service.BookServiceImpl;
import fangwei.solution2.user.domain.AdminUser;
import fangwei.solution2.user.domain.CommonUser;
import fangwei.solution2.user.domain.SuperAdminUser;

public class TestBookServiceImpl {

    private BookService bookService;

    @Before
    public void setUp() throws Exception {
        bookService = new BookServiceImpl();
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testListBook() {
        BookBiz bookBiz = new SuperAdminUser();
        bookService.listBook(bookBiz);
        
        bookBiz = new AdminUser();
        bookService.listBook(bookBiz);
        
        bookBiz = new CommonUser();
        bookService.listBook(bookBiz);
    }

}

 运行测试的输出结果同第一种实现

selectBookList4SuperAdmin
selectBookList4Admin
selectBookList4CommonUser

 

上层如何调用

通常我们都会将User对象放入HttpSession对象中,所以要使用此种实现,我们需要在用户登录成功后,根据不同的角色创建不同的用户类放入HttpSession对象中。登录的代码在这里就略过了,下面给出场景的一种action层实现及测试代码,重点是对HttpSession对象的操作,框架是次要因素

package fangwei.solution2.book.action;

import java.util.List;
import java.util.Map;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.interceptor.SessionAware;

import com.opensymphony.xwork2.ActionSupport;

import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;
import fangwei.solution2.book.service.BookService;

public class BookAction extends ActionSupport implements SessionAware{

    private static final long serialVersionUID = 2744158510001123482L;

    private List<Book> bookList;
    
    private Map<String, Object> session;
    
    private BookService bookService;
    
    public void setBookList(List<Book> bookList) {
        this.bookList = bookList;
    }

    public List<Book> getBookList() {
        return bookList;
    }

    public void setSession(Map<String, Object> session) {
        this.session = session;
    }
    
    public void setBookService(BookService bookService) {
        this.bookService = bookService;
    }
    
    @Action(value="/book/listBook",
        results = {@Result(name=SUCCESS,location="/WEB-INF/jsp/book/listBook.jsp")}
    )
    public void listBook() {
        BookBiz bookBiz = (BookBiz) session.get("user");
        bookList = bookService.listBook(bookBiz);
    }
}

 

package fangwei.solution2.book.action;

import static org.junit.Assert.*;

import java.util.HashMap;
import java.util.Map;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import fangwei.solution2.book.service.BookServiceImpl;
import fangwei.solution2.user.domain.AdminUser;
import fangwei.solution2.user.domain.CommonUser;
import fangwei.solution2.user.domain.SuperAdminUser;
import fangwei.solution2.user.domain.User;

public class TestBookAction {

    private BookAction bookAction;

    @Before
    public void setUp() throws Exception {
        bookAction = new BookAction();
        bookAction.setBookService(new BookServiceImpl());
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testListBook() {
        Map<String, Object> session = new HashMap<String, Object>();
        bookAction.setSession(session);
        
        User user = new SuperAdminUser();
        session.put("user", user);
        bookAction.listBook();
        
        user = new AdminUser();
        session.put("user", user);
        bookAction.listBook();
        
        user = new CommonUser();
        session.put("user", user);
        bookAction.listBook();
    }

}

 

好处在哪里

再回到第一种实现提出的问题,现在如果我们需要增加新的角色类型,就可以创建一个新的用户类XXXUser,然后实现BookBiz等需要的业务接口就可以了。在这种设计下,我们再也不需要满系统去增加else if了。同样的一句代码bookService.listBook(bookBiz);在运行期会有不同的执行效果,这就是OO的多态之一。有人会说,看起来service层的实现没啥用了,只是一个facade了。其实不然,BookBiz抽象出来的是由于角色因素导致的变化,我们依然可以在service中编写所有角色共有的后续业务逻辑,而不必在每个BookBiz的实现中去重复。

package fangwei.solution2.book.service;

import java.util.List;

import fangwei.solution2.book.domain.Book;
import fangwei.solution2.book.domain.BookBiz;

public class BookServiceImpl implements BookService {

    public List<Book> listBook(BookBiz bookBiz) {
        List<Book> listBook = bookBiz.listBook();
        
        //后续的业务逻辑
        //...
        
        return listBook;
    }
    
}

 

疑惑依然存在

第二种实现隔离了由于角色因素导致的变化,使我们能够更方便的增加新的角色类型,但是当需要隔离的变化多了以后会怎么样呢。我们的BookBiz接口中的方法会越来越多,类似BookBiz的接口会越来越多,但是我们的每一种用户类如SuperAdminUser只有一个,那么最后我们的SuperAdminUser类实现的接口会越来越多,即实现的接口方法会越来越多,这个类会不断的膨胀下去导致维护困难。。。这种情况,我们又该如何应对呢?一定还有更优的设计?本文抛砖引玉,旨在了解大家在遇到类似问题时如何用OO的思想去分析解决。

 

分享到:
评论
2 楼 fangwei 2009-08-11  
Qaohao 写道
楼主这种思想和设计模式中策略接近,不知道的理解对不。

特地跑去回顾了一下策略模式,感觉有些相似,策略模式多半使用抽象类+在子类中覆盖抽象类方法实现。
引用
其实我们项目中有遇到过类似的问题,不过很少采用oo的思想去解决。

我个人比较喜欢折腾
1 楼 Qaohao 2009-08-11  
    看了楼主的文章,我加深了对oo的认识。楼主这种思想和设计模式中策略接近,不知道的理解对不。其实我们项目中有遇到过类似的问题,不过很少采用oo的思想去解决。将代码中if-else判断转移给了jvm,一种典型的面向接口编程。恩,收藏了。

相关推荐

    结构化和面向对象编程方法

    面向对象编程(OOP)则在结构化编程的基础上更进一步,它从现实世界中事物的构成和交互中汲取灵感,将数据和方法封装在对象中,并通过对象之间的消息传递来完成任务。OOP的三大核心特性是封装、继承和多态。封装保证...

    Labview面向对象编程

    Labview面向对象编程是NI(National Instruments)的图形化编程环境Labview中的一种高级编程技巧,它借鉴了传统编程语言中的面向对象概念,如封装、继承和多态性,为Labview开发带来了更高的代码复用性和可维护性。...

    PLC的面向对象编程

    Step7中的另一种程序块是函数块(FC),它通常用于结构化编程,类似于计算机编程中的面向过程编程,主要以函数为主体。在施奈德的Unity软件中,面向对象编程的概念同样适用。DFB(分布式功能块)定义中包含了输入/...

    matlab面向对象编程.pdf

    面向对象编程(OO)在软件开发中运用了识别模式和定义分类系统的标准科学与工程实践。分类系统和设计模式使工程师和科学家能够理解复杂系统,并通过重用他人的工作来提高效率。通过将分类系统和设计模式应用于编程,...

    面向对象编程与非面向对象编程

    面向对象编程(Object-Oriented Programming,简称OOP)与非面向对象编程是两种不同的编程范式,它们在软件开发中的应用和设计理念有着显著的区别。本文将深入探讨这两种编程范式的概念、特点及其在实际软件工程中的...

    Matlab面向对象编程

    1. 抽象:在MATLAB面向对象编程中,抽象指的是从实际世界中提取问题的本质特征,并在程序中通过对象来表示这些特征。例如,我们可以创建一个代表汽车的对象,这个对象只需要包含汽车的基本属性如品牌、型号、颜色和...

    第16章 LabVIEW中的面向对象编程,labview面向对象的框架,LabView

    在LabVIEW中实现面向对象编程(Object-Oriented Programming, OOP)可以提升代码的可重用性、可维护性和组织性。本章将深入探讨LabVIEW中的面向对象编程框架及其应用。 面向对象编程的核心概念包括类(Class)、...

    结构化程序设计方法与面向对象程序设计方法之比较.

    结构化程序设计方法与面向对象程序设计方法是两种在软件工程领域内被广泛采用的编程范式,它们各自拥有独特的设计理念、实现方式以及适用场景。接下来,我们将深入探讨这两种方法的基本思想、概念术语、编程语言、...

    面向对象编程思想

    面向对象编程思想 面向对象编程思想是当前计算机界关心的重点,它是 90 年代软件开发方法的主流。面向对象的概念和应用已超越了程序设计和软件开发,扩展到很宽的范围。如数据库系统、交互式界面、应用结构、应用...

    JAVA面向对象编程(孙卫琴)01

    这表明,书中不仅会讲解面向对象的基础知识,还会对比结构化编程与面向对象编程的区别和优势,让读者意识到面向对象编程的重要性和实用性。 5. 本书推荐给希望掌握良好JAVA开发习惯的读者,这意味着书中除了技术和...

    LabVIEW面向对象编程技术.pdf

    该技术基于面向对象编程思想,引入了类和对象的概念,通过类的定义和实例化来实现数据的封装和继承。 在 LabVIEW 中,类是用来表示通用的特性,类的定义包括数据和方法两部分。数据是类的特性,方法是类的行为。...

    面向对象编程(Java).pdf

    面向对象编程是一种编程范式,它以对象为核心组织程序结构,并以类和对象来描述事物。Java是一种广泛使用的面向对象编程语言,它封装了数据和操作数据的行为,并支持继承和多态特性,使得Java程序可以高度模块化和...

    Java数据结构与面向对象编程基础

    数据结构是指在计算机中组织、存储和处理数据的方式,而面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它基于“对象”的概念,使得代码更加模块化、易于理解和维护。 首先,我们来深入了解数据...

    JAVA面向对象编程(孙卫琴)08.pdf

    虽然结构化编程在早期编程中占据主导地位,但随着软件开发复杂度的提升,面向对象编程因其封装性、继承性和多态性的优势,成为了主流的编程范式。 由于文档内容仅提供了部分信息,并没有详细的内容展示,以上知识点...

    C#编程语言与面向对象基础教程

    结构化编程是面向对象编程出现之前的一种主要编程范式。它通过模块化和顺序控制结构来组织代码,强调逻辑清晰和结构明确。然而,随着软件规模的不断扩大,结构化编程逐渐暴露出难以管理和维护的问题。为此,面向对象...

    西门子S7-SCL+结构化控制语言编程.rar

    7. **面向对象编程**:虽然不是原生特性,但可以通过巧妙的编程技巧实现面向对象的概念。 在SCL编程中,通常会涉及到以下概念: - **变量声明**:声明全局变量、局部变量和背景数据块变量,定义其数据类型和初始值...

    JAVA面向对象编程_孙卫琴2.pdf

    ### JAVA面向对象编程的核心知识点解析 #### 一、面向对象编程概述 面向对象编程(Object-...通过对本书的学习,读者不仅可以掌握面向对象编程的基本概念,还能深刻理解面向对象编程相较于传统结构化编程的优势所在。

    c#面向对象编程的小案例 c#经典案例.pdf

    "C#面向对象编程小案例:模拟彩票选号器" 本资源详细介绍了C#面向对象编程的小案例,模拟彩票选号器的实现。该案例主要使用C#语言,通过面向对象编程的思想,实现了一个彩票选号器的模拟。 知识点1:System.Random...

    Labview面向对象编程.zip

    在"Labview面向对象编程"这个主题中,你可能会学习到如何创建类、定义属性和方法,如何实例化对象,以及如何利用继承、封装和多态来优化你的程序设计。此外,还会涉及到对象的生命周期管理、接口设计、异常处理等...

Global site tag (gtag.js) - Google Analytics