博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

继承还是组合

Posted on 2017-09-30 16:47  bw_0927  阅读(528)  评论(0)    收藏  举报

https://www.cnblogs.com/my_life/articles/6197721.html

 

构建新类的两个基本方法是组合和继承,如果你的主要目的是代码重用,那么最好使用组合的方法构造新类,使用继承的方法构造新类造成的可能问题是,无意的修改基类可能会破坏子类的实现。

关于继承关系你可以问自己一个问题,是否它建模了一个 is-a 关系。你能问的另一个问题是,是否客户想要把子类类型当作基类类型来用。

因此在选用组合还是通过继承来构造新类时,需要根据需要选择合适的方法。

 

http://blog.csdn.net/hivon/article/details/583558

现在我们说说其中的一个原则:对类的功能的扩展,要多用组合,少用继承。

对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:
第一、            子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?
第二、            实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。
第三、            继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。
 
嗨!光说这么多一二三有什么用,我们就是想看看实际情况是不是像上面说的那样呢?还是来看看实际的例子吧!
现在我们需要这样一个HashMap,它除了能按常规的Map那样取值,如get(Object obj)。还能按位取值,像ArrayList那样,按存入对象对的先后顺序取值。
对于这样一个问题,我们首先想到的是做一个类,它继承了HashMap类,然后用一个ArrayList属性来保存存入的key,我们按key的位来取值,代码如下:
public class ListMap extends HashMap {
private List list;
public ListMap() {
       super();
        this.list = new ArrayList();
}
public Object put(Object key,Object value)
{
       if(list.contains(key))
        {
               list.remove(key);
        }
       this.list.add(key);
        return super.put(key,value);
}
public Object getKey(int i)
{
        return this.list.get(i);
}
public Object getValue(int i)
{
        return this.get(getKey(i));
}
public int size()
{
        return this.list.size();
}
}
这个ListMap类对HashMap作了一定的扩展,很简单就实现了上面我们所要求的功能。然后我们对该类做一下测试:
ListMap map = new ListMap();
       map.put("a","111");
       map.put("v","190");
       map.put("d","132");
        for(int i=0;i<map.size();i++)
        {
               System.out.println(map.getValue(i));
        }
测试结果为:
111
190
132
正是我们所需要看到的结果。如此说来,这个ListMap类就可以放心的使用了吗?有实现了这样功能的类,你的同事或朋友也可能把这个类拿来使用一下,他可能写出来如下的代码:
ListMap map = new ListMap();
       map.put("a","111");
       map.put("v","190");
       map.put("d","132");
       String[] list = (String[])map.values().toArray(new String[0]);
        for(int i=0;i<list.length;i++)
        {
               System.out.println(list[i]);
        }
运行的结果如下:
132
111
190
哎哟,怎么回事啊?与上面的顺序不对了。你朋友过来找你,说你写的代码怎么不对啊?你很吃惊,说把代码给我看看。于是你看到了上面的代码。你大骂道,混蛋,怎么不是用我的getValue方法啊?你朋友搔搔头道,values方法不是一样的吗?你也没告诉我不能用啊?
通过上面的例子,我们看到了继承的第一个危害:继承不分青红皂白的把父类的公有和受保护的方法统统继承下来。如果你的子类没有对一些方法重写,就会对你的子类产生危害。上面的ListMap类,你没有重写继承自HashMap类的values方法,而该方法仍然是按HashMap的方式取值,没有先后顺序。这时候,如果在ListMap类的对象里使用该方法取得的值,就没有实现我们上面的要求。
接上面的那个例子,你听了朋友的抱怨,摇摇头,想想也是,不能怪他。你只得把values方法在ListMap类重写一遍,然后又嘀咕着,我是不是该把HashMap类的公有方法在ListMap类里全部重写?很多方法根本没有必要用到啊?……
对了,很多方法在ListMap里根本不必用到,但是你用继承的话,还不得不在ListMap里重写它们。如果用组合的话,就没有上面的烦恼了
public class MyListMap {
private HashMap map;
private List list;
public MyListMap()
{
       this.map = new HashMap();
        this.list = new ArrayList();
}
public Object put(Object key,Object value)
{
       if(list.contains(key))
        {
               list.remove(key);
        }
       this.list.add(key);
        return this.map.put(key,value);
}
public Object getKey(int i)
{
        return this.list.get(i);
}
public Object getValue(int i)
{
        return this.map.get(getKey(i));
}
public int size()
{
        return this.list.size();
}
}
这样,你的朋友就只能使用你的getKey和getValue方法了。如果他向你抱怨没有values方法,你尽可以满足他的要求,给他添加上那个方法,而不必担心可能还有方法没有被重写了。
 
我们来看Adapter模式,该模式的目的十分简单:我手里握有一些实现了WhatIHave接口的实现,可我觉得这些实现的功能不够用,我还需要从Resource类里取一些功能来为我所用。Adapter模式的解决方法如下:
public interface WhatIHave
{
        public void g();
}
public class Resource
{
        public void f()
        {
               ……
        }
        public void h()
        {
               ……
        }
}
上面是两个基础类,很明显,我们所要的类既要有g()方法,也要有f()和h()方法。
Public class WhatIWant implements WhatIHave
{
        private Resource res;
        public WhatIWant()
        {
               res = new Resource();
}
public void g()
{
       ……
}
public void f()
{
       this.res.f();
}
public void h()
{
       this.res.h();
}
}
上面就是一个Adapter模式最简单的解决问题的思路。我们主要到,对于Resource类,该模式使用的是组合,而不是继承。这样使用是有多个原因:第一,Java不支持多重继承,如果需要使用好几个不同的Resource类,则继承解决不了问题。第二,如果Resource类还有一个方法:k(),我们在WhatIWant类里使用不上的话,继承就给我们造成多余方法的问题了。
 
如果说Adapter模式对组合的应用的目的十分简单明确,那么Decorator模式对组合的应用简直就是令人叫绝。
让我们还是从Decorator模式的最佳例子说起,咖啡店需要售卖各种各样的咖啡:黑咖啡、加糖、加冰、加奶、加巧克力等等。顾客要买咖啡,他可以往咖啡任意的一种或几种产品。
这个问题一提出来,我们最容易想到的是继承。比如说加糖咖啡是一种咖啡,满足ia a的句式,很明显,加糖咖啡是咖啡的一个子类。于是,我们马上可以赋之行动。
对于咖啡我们做一个咖啡类:Coffee,咖啡加糖:SugarCoffee,咖啡加冰:IceCoffee,咖啡加奶:MilkCoffee,咖啡加巧克力:ChocolateCoffee,咖啡加糖加冰:SugarIceCoffee……
哎哟,我们发现问题了:这样下去我们的类好多啊。可是咖啡店的老板还不放过我们,他又逼着我们增加蒸汽咖啡、加压咖啡,结果我们发现,每增加一种新的类型,我们的类好像是成几何级数增加,我们都要疯了。
这个例子向我们展示了继承的第二个缺点,会使得我们的子类快速的膨胀下去,达到惊人的数量
怎么办?我们的Decorator模式找到了组合来为我们解决问题。下面我们来看看Decorator模式是怎么来解决这个问题的。
首先是它们的共同接口:
public interface Product
{
        public double money();
}
咖啡类:
public class Coffee implements Product
{
        public double money()
        {
               return 12;
}
}
加糖:
public class Sugar implements Product
{
        private Product product;
        public Sugar(Product product)
        {
               this.product = product;
}
public double money()
{
       return product.money+2;
}
}
加冰:
public class Ice implements Product
{
        private Product product;
        public Ice(Product product)
        {
               this.product = product;
}
public double money()
{
       return product.money+1.5;
}
}
加奶:
public class Milk implements Product
{
        private Product product;
        public Milk(Product product)
        {
               this.product = product;
}
public double money()
{
       return product.money+4.0;
}
}
加巧克力:
public class Chocolate implements Product
{
        private Product product;
        public Chocolate(Product product)
        {
               this.product = product;
}
public double money()
{
       return product.money+5.5;
}
}
我们来看客户端的调用。
如果顾客想要黑咖啡,调用如下:
Product prod = new Coffee();
System.out.println(prod.money());
如果顾客需要加冰咖啡,调用如下:
Product prod = new Ice(new Coffee());
System.out.println(prod.money());
如果顾客想要加糖加冰加奶加巧克力咖啡,调用如下:
Product prod = new Chocolate(new Milk(new Ice(new Sugar())));
System.out.println(prod.money());
通过上面的例子,我们可以看到组合的又一个很优越的好处:能够在运行期创建新的对象。如上面我们的加冰咖啡,我们没有这个类,却能通过组合在运行期创建该对象,这的确大大的增加了我们程序的灵活性。
如果咖啡店的老板再要求你增加加压咖啡,你就不会再担心了,只给他增加了一个类就解决了所有的问题。
 
 
 
============
http://www.cnblogs.com/whitewolf/archive/2010/05/03/1726519.html
 
 

 最近一个师弟问某实现是该用对象持有(其实就是组合)还是继承关系实现好。所以就在这里简单的浅谈一下,仅是个人观点,仅供参考。如果写的不对的地方请指出。

     首先它们都是实现系统功能重用,代码复用的最常用的有效的设计技巧,都是在设计模式中的基础结构。

相信大家已了解的,类继承允许我们根据自己的实现来覆盖重写父类的实现细节,父类的实现对于子类是可见的,所以我们一般称之为白盒复用。

对象持有(其实就是组合)要求建立一个好的接口,但是整体类和部分类之间不会去关心各自的实现细节,即它们之间的实现细节是不可见的,故成为黑盒复用。

     继承是在编译时刻静态定义的,即是静态复用,在编译后子类和父类的关系就已经确定了。

而组合这是运用于复杂的设计,它们之间的关系是在运行时候才确定的,即在对对象没有创建运行前,整体类是不会知道自己将持有特定接口下的那个实现类【参考上面的例子中的product接口】。在扩展方面组合比继承更具有广泛性。

    继承中父类定义了子类的部分实现,而子类中又会重写这些实现,修改父类的实现,设计模式中认为这是一种破坏了父类的封装性的表现这个结构导致结果是父类实现的任何变化,必然导致子类的改变。然而组合这不会出现这种现象。

对象的组合还有一个优点就是有助于保持每个类被封装,并被集中在单个任务上(类设计的单一原则)。这样类的层次结构不会扩大,一般不会出现不可控的庞然大类。而类的继承就可能出来这些问题,所以一般编码规范都要求类的层次结构不要超过3层。

组合是大型系统软件实现即插即用时的首选方式

   在设计模式中这两个概念同时出现的地方就是Adapter模式:对象适配(组合)和类适配(继承)

一般我们提倡用对象适配而不是类适配。基于上面的原因。在Java和.NET这些完全面向对象的语言而言类的继承是单继承,取消了C++等的多继承。

下面放两个这两种方式的UML图:

类适配图:

 

    clip_image002

对象适配图:

clip_image002[9]

 

     最后还说一句,“优先使用对象组合,而不是继承”是面向对象设计的第二原则。但并不是说什么都设计都用组合,只是优先考虑组合,更不是说继承即使不好的设计,应该用组合,应为他们之间也有各自的优势。下面是他们之间的优缺点比比较表:

组 合 关 系

继 承 关 系

优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立

缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性

优点:具有较好的可扩展性

缺点:支持扩展,但是往往以增加系统结构的复杂度为代价

优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象

缺点:不支持动态继承。在运行时,子类无法选择不同的父类

优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

缺点:子类不能改变父类的接口

缺点:整体类不能自动获得和局部类同样的接口

优点:子类能自动继承父类的接口

缺点:创建整体类的对象时,需要创建所有局部类的对象

优点:创建子类的对象时,无须创建父类的对象

 
 
 
 
http://blog.csdn.net/qq_31655965/article/details/54645220
类之间的关系大体上存在五种—继承(实现)、依赖、关联、聚合、组合。.
 

继承(实现)

对于类来说,这种关系叫做继承,对于接口来说,这种关系叫做实现。

继承是一种“is-a”关系。

依赖

依赖简单的理解,就是一个类A中的方法使用到了另一个类B。

这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。

比如说,我用笔写字,首先需要一个类来代表我自己,然后需要一个类来代表一支笔,最后,‘我’要调用‘笔’里的方法来写字,用代码实现一下:

public class Pen {
    public void write(){
        System.out.println("use pen to write");
    }
}

public class Me {
    public void write(Pen pen){//这里,pen作为Me类方法的参数
        pen.write();
    }
}

 

看到这大家都懂了,因为这种代码你每天都会写。现在你知道了,这就是一种类与类之间的关系,叫做依赖。

这种关系是一种很弱的关系,但是pen类的改变,有可能会影响到Me类的结果,比如我把pen类write方法的方法体改了,me中再调用就会得到不同的结果。

一般而言,依赖关系在Java中体现为局域变量、方法的形参,或者对静态方法的调用。

关联

关联体现的是两个类、或者类与接口之间语义级别的一种强依赖关系。

这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的。

看下面这段代码:

// pen 还是上面的pen
public class You {
    private Pen pen; // 让pen成为you的类属性 

    public You(Pen p){
        this.pen = p;
    }

    public void write(){
        pen.write();
    }
}

被关联类B以类属性的形式出现在关联类A中,或者关联类A引用了一个类型为被关联类B的全局变量的这种关系,就叫关联关系。

在Java中,关联关系一般使用成员变量来实现。

聚合

聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系

看下面一段代码:

public class Family {
    private List<Child> children; //一个家庭里有许多孩子

    // ...
}

 

在代码层面,聚合和关联关系是一致的,只能从语义级别来区分。普通的关联关系中,a类和b类没有必然的联系,而聚合中,需要b类是a类的一部分,是一种”has-a“的关系,即 a has-a b; 比如家庭有孩子,屋子里有空调。

但是,has 不是 must has,a可以有b,也可以没有。a是整体,b是部分,整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。

不同于关联关系的平等地位,聚合关系中两个类的地位是不平等。

组合

组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。

先看一段代码:

public class Nose {
    private Eye eye = new Eye();  //一个人有鼻子有眼睛
    private Nose nose = new Nose();

    // .... 
}

 

组合同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束。

就像你有鼻子有眼睛,如果你一不小心结束了生命周期,鼻子和眼睛的生命周期也会结束,而且,鼻子和眼睛不能脱离你单独存在。

只看代码,你是无法区分关联,聚合和组合的,具体是哪一种关系,只能从语义级别来区分。

同样,组合关系中,两个类额关系也是不平等的。

组合,聚合和继承

依赖关系是每一个java程序都离不开的,所以就不单独讨论了,普通的关联关系也没有什么特殊的地方,下面我们重点研究一下组合,聚合和继承。

聚合与组合

  1. 聚合与组合都是一种关联关系,只是额外具有整体-部分的意义。

  2. 部件的生命周期不同

    • 聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。

    • 组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时间共享同一个部件。

    这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。

  3. 聚合关系是【has-a】关系,组合关系是【contains-a】关系。

    平时我们只讨论组合和继承的时候,认为组合是【has-a 】关系,而事实上,聚合才是真正的【has-a】关系,组合是更深层次的【contains-a】关系。

    由于【contains-a】关系是一种更深的【has-a】关系,所以说组合是【has-a】关系也是正确的。

组合和继承

这个才是本文的重点。

学过设计模式的都知道,要“少用继承,多用组合”,这究竟是为什么呢?

我们先来看一下组合和继承各自的优缺点:

组合和继承的优缺点

组合

优点:

- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立
- 具有较好的可扩展性
- 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象
- 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

缺点:

- 整体类不能自动获得和局部类同样的接口
- 创建整体类的对象时,需要创建所有局部类的对象

缺点分析:

1、整体类不能自动获得和局部类同样的接口

如果父类的方法子类中几乎都要暴露出去,这时可能会觉得使用组合很不方便,使用继承似乎更简单方便。但从另一个角度讲,实际上也许子类中并不需要暴露这些方法,客户端组合应用就可以了。所以上边推荐不要继承那些不是为了继承而设计的类,一般为了继承而设计的类都是抽象类。

2、创建整体类的对象时,需要创建所有局部类的对象

这个可能没什么更好的办法,但在实际应用中并没有多出多少代码。

继承

优点:

- 子类能自动继承父类的接口
- 创建子类的对象时,无须创建父类的对象

缺点:

- 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
- 支持扩展,但是往往以增加系统结构的复杂度为代价
- 不支持动态继承。在运行时,子类无法选择不同的父类
- 子类不能改变父类的接口

缺点分析:

1、为什么继承破坏封装性?

这里写图片描述

鸭子中不想要“飞”的方法,但因为继承无法封装这个无用的“飞”方法 。

2、为什么继承紧耦合:

这里写图片描述

当作为父类的BaseTable中感觉Insert这个名字不合适时,如果希望将其修改成Create方法,那使用了子类对象Insert方法将会编译出错,可能你会觉得这改起来还算容易,因为有重构工具一下子就好了并且编译错误改起来很容易。

但如果BaseTable和子类在不同的程序集中,维护的人员不同,BaseTable程序集升级,那本来能用的代码忽然不能用了,这还是很难让人接受的

3、为什么继承扩展起来比较复杂

这里写图片描述

当图书和数码的算税方式和数码产品一样时,而消费类产品的算税方式是另一种方式时,如果采用继承方案可能会演变成如下方式:

这里写图片描述

这样如果产品继续增加,算税方式继续增加,那继承的层次会非常复杂,而且很难控制,而使用组合就能很好的解决这个问题

4、继承不能支持动态继承

这个其实很好理解,因为继承是编译期就决定下来的,无法在运行时改变,如3例中,

如果用户需要根据当地的情况选择计税方式,使用继承就解决不了,而使用组合结合反射就能很好的解决。

5、为什么继承,子类不能改变父类接口

如2中的图,子类中觉得Insert方法不合适,希望使用Create方法,因为继承的原因无法改变

组合与继承的区别和联系

  • 在继承结构中,父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种 白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性)

  • 组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用 。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)

  • 继承在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)

  • 组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。

  • 组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。

  • 组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。

  • 当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。

  • 最重要的一点,使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。

  • 还有一点需要注意,如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。

  • 从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。

  • 这两者的区别主要体现在类的抽象阶段,在分析类之间的关系时就应该确定是采用组合还是采用继承。

  • 引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活”。

继承还是组合?

首先它们都是实现系统功能重用,代码复用的最常用的有效的设计技巧,都是在设计模式中的基础结构。

很多人都知道面向对象中有一个比较重要的原则『多用组合、少用继承』或者说『组合优于继承』。从前面的介绍已经优缺点对比中也可以看出,组合确实比继承更加灵活,也更有助于代码维护。

所以,建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效。

注意,并不是说继承就一点用都没有了,前面说的是【在同样可行的情况下】。有一些场景还是需要使用继承的,或者是更适合使用继承。

继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。

只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在 is-a 关系的时候,类B才应该继承类A。

向上转型将会在下一篇《重新认识Java(五) — 面向对象之多态》中详细讲解。

总结

根据我们前面讲的内容我们可以发现继承的缺点远远多于优点,尽管继承在学习OOP的过程中得到了大量的强调,但并不意味着应该尽可能地到处使用它。相反,使用它时要特别慎重。

只有在清楚知道继承在所有方法中最有效的前提下,才可考虑它。 继承最大的优点就是扩展简单,但大多数缺点都很致命,但是因为这个扩展简单的优点太明显了,很多人并不深入思考,所以造成了太多问题。

最后,总结一下:

1、精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不要多于三层。 
2、对于不是专门用于被继承的类,禁止其被继承。 
3、优先考虑用组合关系来提高代码的可重用性。 
4、子类是一种特殊的类型,而不只是父类的一个角色 
5、子类扩展,而不是覆盖或者使父类的功能失效

没错,写了这么多,就是想说: 请慎重使用继承,除非你确定非用继承不可!


这篇文章写得比较粗糙,因为写文章的时候一直在拉肚子。。。以后还会做一些修改,暂时先这样。如果文中有错误或者有更好的解释,欢迎给我留言。我也只是一个学习的人,而不是一个Java大神,所以不保证文章内容的正确性~