Python Flask高级编程之从0到1开发《鱼书》精品项目 ☝☝☝

一 、安装环境
我们使用 flask web框架,并用 sqlalchemy来做数据库映射,并使用 migrate做数据迁移。

$ pip install flask

$ pip install SQLAlchemy==0.7.9

$ pip install flask-sqlalchemy

$ pip install flask-migrate
$ pip install sqlalchemy-migrate

二、建立项目
flask 没有 django 那样原生的 manage管理工具(flask-admin可以实现,日后再说)。因此我们需要手动建立目录。新建一个 myproject目录,在里面建 app tmp两个文件夹,然后在 app文件夹里面建立 static, templates 两个文件夹,用来存储静态文件和模板。最后目录结构如下:

├── app

│ ├── static

│ ├── templates

└── tmp

三、 初始化文件

建立一些文件,在 app文件里建立 __init__.py models.py views.py 然后在与app 同目录下建立 config.py 和 run.py 此时的目录结构如下:

(env)admin@admindeMacBook-Air:~/project/python/flask/project$ tree

├── app

│ ├── static

│ ├── templates

│ ├── __init__.py

│ ├── models.py

│ ├── views.py

├── config.py

├── run.py

└── tmp

四 、开始项目

1 hello wrod
打开 (/app/__init.py) 文件,写入

# -*- coding: utf-8 -*-from flask import Flask # 引入 flask

app = Flask(__name__) # 实例化一个flask 对象import views # 导入 views 模块# from app import views

注意,我们的 app 文件夹其实是一个python包,from app import views 这句话也就是从 包app里面导入 views模块,所以写成注释的那句话也没错,其他代码实践喜欢写成后面那种

现在我们来开始写我们的视图,打开 (/app/views.py)

# -*- coding: utf-8 -*-from app import appfrom models import User, Post, ROLE_USER, ROLE_ADMIN

@app.route(‘/’)def index():    return ‘hello world, hello flask’

下一步我们将要启动我们的开发服务器,打开 (/run.py)

# -*- coding: utf-8 -*-from app import appif __name__ == ‘__main__’:

app.debug = True # 设置调试模式,生产模式的时候要关掉debug

app.run() # 启动服务器

这段代码需要注意第一句 from app import app。也许会有疑问,我们的 app 包里,貌似没有 app.py 这样的模块。其实这是 flask 方式和python的导入方式。from app 指导入 app 包里的 __iniy__.py 所以这句话的含义是导入 __.init__.py 里面的 app实例。

打开shell 运行

$ python run.py

(env)admin@admindeMacBook-Air:~/project/python/flask/project$ python run.py
* Running on http://127.0.0.1:5000/

* Restarting with reloader

用浏览器打开 http://127.0.0.1:5000/ 将会看到一个输入 hello world 的页面

2 连接数据库
下面开始连接数据库引擎 先要做一个简单的配置 打开 (/config.py)

# -*- coding: utf-8 -*-

import os

basedir = os.path.abspath(os.path.dirname(__file__))

SQLALCHEMY_DATABASE_URI = ‘sqlite:///%s’ % os.path.join(basedir, ‘app.db’)

SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, ‘db_repository’)

CSRF_ENABLED = True

SECRET_KEY = ‘you-will-never-guess’

SQLALCHEMY_DATABASE_URI是the Flask-SQLAlchemy必需的扩展。这是我们的数据库文件的路径。

SQLALCHEMY_MIGRATE_REPO 是用来存储SQLAlchemy-migrate数据库文件的文件夹。

然后打开 (/app/__init__.py)

# -*- coding: utf-8 -*-import osfrom flask import Flaskfrom flask.ext.sqlalchemy import SQLAlchemyfrom config import basedir

app = Flask(__name__)

app.config.from_object(‘config’) # 载入配置文件

db = SQLAlchemy(app) # 初始化 db 对象

# from app import views, models # 引用视图和模型

import views, models

打开 (/app/models.py)

# -*- coding: utf-8 -*-

from app import db

ROLE_USER = 0

ROLE_ADMIN = 1
class User(db.Model):

id = db.Column(db.Integer, primary_key=True)

nickname=db.Column(db.String(60), index=True, unique=True)

email = db.Column(db.String(120), index=True, unique=True)

role = db.Column(db.SmallInteger, default=ROLE_USER)    
    def __repr__(self):        return ‘<User %r>’ % self.nickname

我们的模型建立了一个 user类,正好是 数据库里面的 user 表,表有四个字段

现在我们的数据库配置已经好了,可是还没有建立数据库。新建一个文件 (/db_create.py)

# -*- coding: utf-8 -*-

from migrate.versioning import apifrom config import SQLALCHEMY_DATABASE_URIfrom config import SQLALCHEMY_MIGRATE_REPOfrom app import dbimport os.path

db.create_all()

if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):

api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')

api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)else:

api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

这个脚本是完全通用的,所有的应用路径名都是从配置文件读取的。当你用在自己的项目时,你可以把脚本拷贝到你的项目目录下就能正常使用了。
打开shell 运行

$ python db_create.py

运行这条命令之后,你就创建了一个新的app.db文件。这是个支持迁移的空sqlite数据库,同时也会生成一个带有几个文件的db_repository目录,这是SQLAlchemy-migrate存储数据库文件的地方,注意如果数据库已存在它就不会再重新生成了。这将帮助我们在丢失了现有的数据库后,再次自动创建出来。
运行
slqite3 app.db

>>> .tables

>>> user

或者使用 sqlite 图形化客户端,没有需要安装 (windows 下直接下载安装包即可)

$ sudo apt-get install sqlitebrowser
3 数据库迁移
每当我们更改了 models 。等价于更改了数据库的表结构,这个时候,需要数据库同步。新建 (/db_migrate.py)

# -*- coding: utf-8 -*-

import impfrom migrate.versioning import apifrom app import dbfrom config import SQLALCHEMY_DATABASE_URIfrom config import SQLALCHEMY_MIGRATE_REPO

migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1)

tmp_module = imp.new_module('old_model')

old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

exec old_model in tmp_module.__dict__

script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)
open(migration, 'wt').write(script)

api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

print 'New migration saved as' + migrationprint 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

这个脚本看起来很复杂,SQLAlchemy-migrate通过对比数据库的结构(从app.db文件读取)和models结构(从app/models.py文件读取)的方式来创建迁移任务,两者之间的差异将作为一个迁移脚本记录在迁移库中,迁移脚本知道如何应用或者撤销一次迁移,所以它可以方便的升级或者降级一个数据库的格式。

开始数据库迁移

$ python db_mirgate.py

New migration saved as db_repository/versions/001_migration.py Current database version: 1
相应的,我们继续创建数据库 升级和回退的脚本
(/db_upgrade.py)

# -*- coding: utf-8 -*-

from migrate.versioning import apifrom config import SQLALCHEMY_DATABASE_URIfrom config import SQLALCHEMY_MIGRATE_REPO

api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

(/db_downgrade.py)

# -*- coding: utf-8 -*-

from migrate.versioning import apifrom config import SQLALCHEMY_DATABASE_URIfrom config import SQLALCHEMY_MIGRATE_REPO

v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v – 1)

print 'Current database version:' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

4 操作数据库
接下来,我们定义一个新的models class 并做第二次迁移归并
打开(/app/models.py)

# -*- coding: utf-8 -*-

from app import db

ROLE_USER = 0

ROLE_ADMIN = 1

class User(db.Model):

id = db.Column(db.Integer, primary_key=True)

nickname=db.Column(db.String(60), index=True, unique=True)

email = db.Column(db.String(120), index=True, unique=True)

role = db.Column(db.SmallInteger, default=ROLE_USER)

posts = db.relationship(‘Post’, db.backref = ‘author’, db.lazyload = ‘dynamic’)

def __repr__(self):        return ‘<User %r>’ % self.nickname

class Post(db.Model):

id = db.Column(db.Integer, primary_key=True)

body = db.Column(db.String(140))

timestamp = db.Column(db.DateTime)

user_id = db.Column(db.Integer, db.ForeignKey(‘user.id’))

def __repr__(self):        return ‘<Post %r>’ % (self.body)

这里我们进行了两个class 的关联。

$ python db_mirgrate.py

New migration saved as db_repository/versions/002_migration.py Current database version: 2

打开 (/app/views.py)

# -*- coding: utf-8 -*-

from flask import render_template, flash, redirect, session, url_for, request, gfrom app import app, dbfrom models import User, Post, ROLE_USER, ROLE_ADMIN

@app.route(‘/’)def index():    return render_template(‘index.html’)

@app.route(‘/adduser/<nickname>/<email>’)def adduser(nickname, email):

u = User(nickname=nickname, email=email)    try:

db.session.add(u)

db.session.commit()        return ‘add successful’    except Exception, e:        return ‘something go wrong’

@app.route(‘/getuser/<nickname>’)def getuser(nickname):

user = User.query.filter_by(nickname=nickname).first()        return render_template(‘user.html’, user=user)

@app.errorhandler(404)def internal_error(error):    return render_template(‘404.html’), 404

@app.errorhandler(500)def internal_error(error):

db.session.rollback()    return render_template(‘500.html’), 500

这次我们使用了模板, 新建(/app/templates/user.html)

<html><head><title>user</title></head><body><h1>user</h1><ul><li>user: {{ user.nickname }}</li><li>email: {{ user.email }}</li></ul></body></html>

最后运行

$ python run.py
用浏览器访问 http://127.0.0.1:5000/adduser/username/useremail
最后还可以用 sqlite 客户端打开查看我们的数据库变换。关于更多的数据库操作方法,请阅读 sqlalchemy文档。
最后我们的代码目录结构如下:

(env)admin@admindeMacBook-Air:~/project/python/flask/project$ tree

.

├── app

│ ├── __init__.py

│ ├── models.py

│ ├── static

│ ├── templates

│ │ ├── 404.html

│ │ ├── 500.html

│ │ ├── index.html

│ │ └── user.html

│ ├── views.py

├── app.db

├── config.py

├── db_create.py

├── db_downgrade.py

├── db_migrate.py

├── db_repository

│ ├── __init__.py

│ ├── manage.py

│ ├── migrate.cfg

│ ├── README

│ └── versions

│ ├── 001_migration.py

│ ├── 002_migration.py

│ ├── __init__.py

├── db_upgrade.py

├── requirements.txt

├── run.py

├── tmp

5 表单
接下来,我们将要接触表单方面的内容。flask原生提供的表单处理,也比较简单。当然,有了轮子,我们就直接用好了。需要安装一个
Flask-WTF==0.9.4。
安装完之后,打开 (/app/forms.py)

# -*- coding: utf-8 -*-

import refrom flask_wtf import Formfrom flask_wtf.html5 import EmailFieldfrom wtforms import TextField, PasswordField, BooleanFieldfrom wtforms.validators import DataRequired, ValidationErrorfrom models import Userfrom hashlib import md5

class RegisterFrom(Form):

nickname = TextField(‘nickname’, validators=[DataRequired()])

email = EmailField(’email’, validators=[DataRequired()])

password = PasswordField(‘password’, validators=[DataRequired()])

conform = PasswordField(‘conform’, validators=[DataRequired()])

def validate_nickname(self, field):

nickname = field.data.strip()if len(nickname) < 3 or len(nickname) > 20:    raise ValidationError(‘nickname must be 3 letter at least’)        elif not re.search(r’^\w+$’, nickname):            raise ValidationError(‘User names can contain only alphanumeric characters and underscores.’)else:# 验证是否已经注册            u = User.query.filter_by(nickname=nickname).first()            if u:raise ValidationError(‘The nickname already exists’)

def validate_email(self, field):

email = field.data.strip()

email = User.query.filter_by(email=email).first()        if email:            raise ValidationError(‘The email already exists’)

def validate_password(self, field):

password = field.data.strip()        if len(password) < 6:            raise ValidationError(‘password must be 3 letter at least’)

def validate_conform(self, field):

conform = field.data.strip()        if self.data[‘password’] != conform:            raise ValidationError(‘the password and conform are different’)

上述代码定义了一个 RegisterFrom class,正好是一个表单,class的字段映射为表单的域。其中 wtforms 中有 TextField, PasswordField, BooleanField这些表单类型,flask_wtf.html5 中 EmailField 则是 html5 规范的。
表单有 form.validate_on_submit() 方法,这个方法执行之前会验证表单域。验证方法和django类似,都是 validate_field 的方法。其中一个参数 field正好是表单的value值 需要注意验证两次密码是否相同的时候,需要用到 self.data 。这是一个字典,包含了表单域的name和value

6 用户注册
打开(/app/views.py)添加注册方法。

@app.route(‘/account/signup’, methods=[‘GET’, ‘POST’])def signup():

form = RegisterFrom()if request.method == ‘POST’:if form.validate_on_submit():

psdmd5 = md5(form.data[‘password’])

password = psdmd5.hexdigest()

u = User(nickname=form.data[‘nickname’], email=form.data[’email’], password=password)try:

db.session.add(u)

db.session.commit()

flash(‘signup successful’)
except Exception, e:    return ‘something goes wrong’    return redirect(url_for(‘signin’))

return render_template(‘signup.html’, form=form)

模版 新建 (/app/tempaltes/signup.html)

<!– extend base layout –>

{% extends “base.html” %}

{% block content %}<form method=”POST” action=”>

{{ form.hidden_tag() }}<p>{{ form.nickname.label }} {{ form.nickname(size=20) }}</p><p>{{ form.email.label }} {{ form.email(size=20) }}</p><p>{{ form.password.label }} {{ form.password(size=20) }}</p><p>{{ form.conform.label }} {{ form.conform(size=20) }}</p><input type=”submit” value=”Sign Up”><input type=”reset” value=”Reset”></form>

{{ form.errors }}

{% endblock %}

其中,主要是用到 form 对象的一些属性。form.nickname.label 是指表单类定义的名字,form.nickname 会转变成 表单域的 html 代码 即 <input type=”text” id=”nickname” name=”nickaname” value=””/>

7 用户登录
先添加一个用户登录的表单,打开 (/app/forms.py)

class LoginForm(Form):

nickname = TextField(‘nickname’, validators=[DataRequired()])

password = PasswordField(‘password’, validators=[DataRequired()])

remember_me = BooleanField(‘remember_me’, default=False)

def validate_nickname(self, field):

nickname = field.data.strip()        if len(nickname) < 3 or len(nickname) > 20:            raise ValidationError(‘nickname must be 3 letter at least’)        elif not re.search(r’^\w+$’, nickname):            raise ValidationError(‘User names can contain only alphanumeric characters and underscores.’)        else:            return nickname

用户登录,最简单的方法就是 验证用户名,如果通过,则添加 session,登出的时候,清除session即可,模版里面可以直接使用 request.session用来判断用户登录状态
打开(/app/views.py) 添加登录登出方法

@app.route(‘/account/signin’, methods=[‘GET’, ‘POST’])def signin():

form = LoginForm()if request.method == ‘POST’:if form.validate_on_submit():

nickname = form.data[‘nickname’]

psdmd5 = md5(form.data[‘password’])

password = psdmd5.hexdigest()

u = User.query.filter_by(nickname=nickname, password=password).first()if u:

session[‘signin’] = True

flash(‘signin successful’)                return redirect(url_for(‘index’))else:

flash(u’用户名或者密码错误’)                return render_template(‘signin.html’, form=form)

@app.route(‘/account/signout’)def signout():

session.pop(‘signin’, None)

flash(‘signout successful’)    return redirect(‘index’)

注意,我们使用 flask 的时候,用了中文,就必须是 unicode
除了原生的登录方式,我们还可以用flask-login。安装flask-login
此时需要初始化一个 login_manager 对象,打开 (/app/__init__.py)

# -*- coding: utf-8 -*-

import osfrom flask import Flaskfrom flask.ext.sqlalchemy import SQLAlchemyfrom flask.ext.login import LoginManagerfrom config import basedir

app = Flask(__name__)
app.config.from_object(‘config’)

db = SQLAlchemy(app) # 初始化数据库管理对象login_manager = LoginManager() # 初始化登录管理对象login_manager.init_app(app)

login_manager.login_view = “signin” # 登录跳转视图login_manager.login_message = u”Bonvolu ensaluti por uzi tio paĝo.” # 登录跳转视图前的输出消息

# from app import views, models

import views, models

然后打开 (/app/views.py)
添加下面方法, 注释掉之前的 登入登出方法

from flask.ext.login import login_user, logout_user, current_user, login_requiredfrom app import app, db, login_manager

@login_manager.user_loaderdef load_user(userid):return User.query.get(userid)

@app.route(‘/account/signin’, methods=[‘GET’, ‘POST’])def signin():

form = LoginForm()    if request.method == ‘POST’:        if form.validate_on_submit():

nickname = form.data[‘nickname’]

psdmd5 = md5(form.data[‘password’])

password = psdmd5.hexdigest()

remember_me = form.data[‘remember_me’]

user = User.query.filter_by(nickname=nickname, password=password).first()if user:

login_user(user, remember = remember_me)

flash(‘signin successful’)                return redirect(request.args.get(“next”) or url_for(“index”))            else:

flash(u’用户名或者密码错误’)return render_template(‘signin.html’, form=form)

@app.route(‘/account/signout’)

@login_requireddef signout():
    logout_user()

flash(‘signout successful’)return redirect(‘index’)

模版也会响应的改,具体看源码吧。
base.html

<html><head><meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />

{% if title %}<title>{{title}} – microblog</title>

{% else %}<title>microblog</title>

{% endif %}</head><body><div class="metanav">

<div><a href=”{{ url_for(‘index’) }}”>Home</a>

{% if current_user.is_authenticated() %}<a href=”{{ url_for(‘signout’) }}”>signout</a>

{% else %}<a href=”{{ url_for(‘signin’) }}”>signin</a>

{% endif %}</div>

{% for message in get_flashed_messages() %}<div class="flash">{{ message }}</div>

{% endfor %}

{% block content %}{% endblock %}</body></html>

Python Flask高级编程之从0到1开发《鱼书》精品项目 ☝☝☝的更多相关文章

  1. Python Flask高级编程之从0到1开发《鱼书》精品项目 ✍✍✍

    Python Flask高级编程之从0到1开发<鱼书>精品项目  一 .安装环境我们使用 flask web框架,并用 sqlalchemy来做数据库映射,并使用 migrate做数据迁移 ...

  2. Python Flask高级编程之从0到1开发《鱼书》精品项目

    Python Flask高级编程之从0到1开发<鱼书>精品项目     整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感 ...

  3. Python Flask高级编程之RESTFul API前后端分离精讲 (网盘免费分享)

    Python Flask高级编程之RESTFul API前后端分离精讲 (免费分享)  点击链接或搜索QQ号直接加群获取其它资料: 链接:https://pan.baidu.com/s/12eKrJK ...

  4. Python进阶:并发编程之Futures

    区分并发和并行 并发(Concurrency). 由于Python 的解释器并不是线程安全的,为了解决由此带来的 race condition 等问题,Python 便引入了全局解释器锁,也就是同一时 ...

  5. Python Flask高级编程

    第1章 课程导语介绍课程的内容1-1 开宗明义 试看1-2 课程维护与提问 第2章 Flask的基本原理与核心知识本章我们首先介绍Python官方推荐的最佳包与虚拟环境管理工具:Pipenv.接着我们 ...

  6. python的多线程编程之锁

    1. 背景概述 在上篇文章中,主要讲述了python中的socket编程的一些基本方面,但是缺少关于锁的相关概念,从而在这篇文章中进行补充. 由于在python中,存在了GIL,也就是全局解释器锁,从 ...

  7. 学习python-20191203-Python Flask高级编程开发鱼书_第02章 Flask的基本原理与核心知识

    视频01: 做一个产品时,一定要对自己的产品有一个明确的定位.并可以用一句话来概括自己产品的核心价值或功能. 鱼书网站几个功能 1.选择要赠送的书籍,向他人赠送书籍(价值功能,核心价值的主线): 2. ...

  8. python高级编程之 web静态服务器

    返回固定数据 import socket def request_handler(new_client_socket): """ 响应客户端请求的核心函数 "& ...

  9. Python之网络编程之concurrent.futures模块

    需要注意一下不能无限的开进程,不能无限的开线程最常用的就是开进程池,开线程池.其中回调函数非常重要回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去自己加 ...

随机推荐

  1. 编写一个函数来反转某个二进制型里的字节顺序(erlang)

    reverse_byte(<<>>) -> <<>>; reverse_byte(<<Header:8, Tail/bits>& ...

  2. 056 模块7-os库的基本使用

    目录 一.os库基本介绍 二.os库之路径操作 2.1 路径操作 三.os库之进程管理 3.1 进程管理 四.os库之环境参数 4.1 环境参数 一.os库基本介绍 os库提供通用的.基本的操作系统交 ...

  3. ThreadLocal的认知与见解

    ThreadLocal:提高一个线程的局部变量,访问某个线程拥有自己局部变量(很难理解.看看下面这句话,顺便再讲个例子). 当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该 ...

  4. 从原理到场景 系统讲解 PHP 缓存技术

    第1章课程介绍 此为PHP相关缓存技术的课堂,有哪些主流的缓存技术可以被使用? 第1章 课程介绍 1-1课程介绍1-2布置缓存的目的1-3合理使用缓存1-4哪些环节适合用缓存 第2章 文件类缓存 2- ...

  5. 简说Python发展及其就业前景

    简说python 发展历史 Python是著名的"龟叔"Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言. python从ABC语言 ...

  6. 跟我学SpringCloud | 第二十章:Spring Cloud 之 okhttp

    1. 什么是 okhttp ? okhttp 是由 square 公司开源的一个 http 客户端.在 Java 平台上,Java 标准库提供了 HttpURLConnection 类来支持 HTTP ...

  7. 2018年蓝桥杯java b组第二题

    2.标题:方格计数 如图p1.png所示,在二维平面上有无数个1x1的小方格. 我们以某个小方格的一个顶点为圆心画一个半径为1000的圆.你能计算出这个圆里有多少个完整的小方格吗? 注意:需要提交的是 ...

  8. 夯实Java基础系列9:深入理解Class类和Object类

    目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...

  9. vue-router之路由元信息

    路由元信息?(黑人问号脸???)是不是这么官方的解释很多人都会一脸懵?那么我们说meta,是不是很多人恍然大悟,因为在项目中用到或者看到过呢? 是的,路由元信息就是我们定义路由时配置的meta字段:那 ...

  10. 23种设计模式之代理模式(Proxy Pattern)

    在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代理对象代替 ...