`
有崖生110
  • 浏览: 53092 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

接口、抽象类和工厂

 
阅读更多

接口:

Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。 接口的两种含义:一,Java接口,Java语言中存在的结构,有特定的语法和结构;二,一个类所具有的方法的特征集合,是一种逻辑上的抽象。前者叫做“Java接口”,后者叫做“接口”。

Java接口本身没有任何实现,因为Java接口不涉及表象,而只描述public行为,所以Java接口比Java抽象类更抽象化。但是接口不是类,不能使用new 运算符实例化一个接口。如 x=new comparable(......);//这个是错误来的。但是可以声明接口变量Comparable x; //这是允许的。


接口的特征归纳:


1, Java接口中的成员变量默认都是public,static,final类型的(都可省略),必须被显示初始化,即接口中的成员变量为常量(大写,单词之间用"_"分隔)


2, Java接口中的方法默认都是public,abstract类型的(都可省略),没有方法体,不能被实例化 


public interface A  

   {  

    int CONST = 1; //合法,CONST默认为public,static,final类型  

    void method(); //合法,method()默认为public,abstract类型  

    public abstract void method2(); //method2()显示声明为public,abstract类型  

   } 

3, Java接口中只能包含public,static,final类型的成员变量和public,abstract类型的成员方法


public interface A  

  {  

   int var; //错,var是常量,必须显示初始化   

   void method(){...};   //错,接口中只能包含抽象方法  

   protected void method2(); //错,接口中的方法必须是public类型  

   static void method3(){...};   //错,接口中不能包含静态方法  

  } 

4, 接口中没有构造方法,不能被实例化


public interface A  

  {  

   public A(){...}; //错,接口中不能包含构造方法  

   void method();  

  } 

5, 一个接口不能实现(implements)另一个接口,但它可以继承多个其它的接口


public interface A  

 {  

  void methodA();  

 }  

 public interface B  

 {  

  void methodB();  

 }  

 public interface C extends A, B   //C称为复合接口  

 {  

  void methodC();  

 }  

 public interface C implements A{...}   //错 


6, Java接口必须通过类来实现它的抽象方法


public class A implements B{...}


7, 当类实现了某个Java接口时,它必须实现接口中的所有抽象方法,否则这个类必须声明为抽象的


8, 不允许创建接口的实例(实例化),但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例


public class B implements A{}  

   A a = new B(); //引用变量a被定义为A接口类型,引用了B实例  

   A a = new A(); //错误,接口不允许实例化 

9, 一个类只能继承一个直接的父类,但可以实现多个接口,间接的实现了多继承.


public class A extends B implements C, D{...} //B为class,C,D为interface


接口与抽象类的区别:


Java接口和Java抽象类最大的一个区别,就在于Java抽象类可以提供某些方法的部分实现,而Java接口不可以,这大概就是Java抽象类唯一的优点吧,但这个优点非常有用。如果向一个抽象类里加入一个新的具体方法时,那么它所有的子类都一下子都得到了这个新方法,而Java接口做不到这一点,如果向一个Java接口里加入一个新方法,所有实现这个接口的类就无法成功通过编译了,因为你必须让每一个类都再实现这个方法才行,这显然是Java接口的缺点。

一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的等级结构中,而由于Java语言的单继承性,所以抽象类作为类型定义工具的效能大打折扣。在这一点上,Java接口的优势就出来了,任何一个实现了一个Java接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个Java接口,从而这个类就有了多种类型。

不难看出,Java接口是定义混合类型的理想工具,混合类表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为。

在语法上,抽象类和接口有着以下不同:

1.abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。 继承抽象类使用的是extends关键字,实现接口使用的是implements关键字,继承写在前面,实现接口写在后面。如果实现多个接口,中间用逗号分隔。例:

public class Main extends JApplet

public class Main implements Runnable

public class Main extends JApplet implements ActionListener

public class Main extends JApplet implements ActionListener, Runnable


2.在abstract class中可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。


3.abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。


4.实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。


5.接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。


6.抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以重新赋值。


7.接口中的方法默认都是 public,abstract 类型的。



2者设计理念区别---


abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。 


考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示: 


使用abstract class方式定义Door: 


abstract class Door { 

abstract void open(); 

abstract void close(); 



使用interface方式定义Door: 



interface Door { 

void open(); 

void close(); 



其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 


如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。 


解决方案一: 


简单的在Door的定义中增加一个alarm方法,如下: 


abstract class Door { 

abstract void open(); 

abstract void close(); 

abstract void alarm(); 



或者 


interface Door { 

void open(); 

void close(); 

void alarm(); 



那么具有报警功能的AlarmDoor的定义方式如下: 


class AlarmDoor extends Door { 

void open() { … } 

void close() { … } 

void alarm() { … } 



或者 


class AlarmDoor implements Door { 

void open() { … } 

void close() { … } 

void alarm() { … } 

} 


这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。 


解决方案二: 


既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。 


显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。 


如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。 


如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示: 


abstract class Door { 

abstract void open(); 

abstract void close(); 

interface Alarm { 

void alarm(); 

class AlarmDoor extends Door implements Alarm { 

void open() { … } 

void close() { … } 

void alarm() { … } 



这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。



工厂模式---



工厂模式在《Java与模式》中分为三类:

1)简单工厂模式(Simple Factory):不利于产生系列产品;


2)工厂方法模式(Factory Method):又称为多形性工厂;


3)抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品;

             这三种模式从上到下逐步抽象,并且更具一般性。

             GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。



二、简单工厂模式


简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。


在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定那一个产品类应当被实例化, 如同一个交通警察站在来往的车辆流中,决定放行那一个方向的车辆向那一个方向流动一样。

        先来看看它的组成:


         1) 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。


         2) 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。


         3) 具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。


三、工厂方法模式


工厂方法模式是简单工厂模式的进一步抽象化和推广,工厂方法模式里不再只由一个工厂类决定那一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做。

  来看下它的组成:


       1)抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。


       2)具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。


       3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。


       4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

       工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来——当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代 码。可以看出工厂角色的结构也是符合开闭原则的!

那么简单工厂模式怎么用呢?我来举个例子吧,我想这个比讲一大段理论上的文字描述要容易理解的多!下面就来给那个暴发户治病: P 
在使用了简单工厂模式后,现在暴发户只需要坐在车里对司机说句:"开车"就可以了。来看看怎么实现的: 
//抽象产品角色 
public interface Car{ 
public void drive(); 
//具体产品角色 
public class Benz implements Car{ 
public void drive() { 
System.out.println("Driving Benz "); 
public class Bmw implements Car{ 
public void drive() { 
System.out.println("Driving Bmw "); 
。。。(奥迪我就不写了:P) 
//工厂类角色 
public class Driver{ 
//工厂方法 
//注意 返回类型为抽象产品角色 
public static Car driverCar(String s)throws Exception { 
//判断逻辑,返回具体的产品角色给Client 
if(s.equalsIgnoreCase("Benz")) return new Benz(); 
else if(s.equalsIgnoreCase("Bmw")) 
return new Bmw(); 
...... 
else throw new Exception(); 
。。。 
//欢迎暴发户出场...... 
public class Magnate{ 
public static void main(String[] args){ 
try{ 
//告诉司机我今天坐奔驰 
Car car = Driver.driverCar("benz"); 
//下命令:开车 
car.drive(); 
。。。 
如果将所有的类放在一个文件中,请不要忘记只能有一个类被声明为public。 程序中类之间的关系如下: 
这便是简单工厂模式了。下面是其好处: 
首先,使用了简单工厂模式后,我们的程序不在"有病",更加符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责"消费"产品(正如暴发户所为)。 
下面我们从开闭原则上来分析下简单工厂模式。当暴发户增加了一辆车的时候,只要符合抽象产品制定的合同,那么只要通知工厂类知道就可以被客户使用了。那么对于产品部分来说,它是符合开闭原则的--对扩展开放、对修改关闭;但是工厂部分好像不太理想,因为每增加一辆车,都要在工厂类中增加相应的商业逻辑和判断逻辑,这显自然是违背开闭原则的。 
对于这样的工厂类(在我们的例子中是为司机师傅),我们称它为全能类或者上帝类。 
我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝类坏了,进而累坏了我们可爱的程序员:( 
正如我前面提到的简单工厂模式适用于业务将简单的情况下。而对于复杂的业务环境可能不太适应阿。这就应该由工厂方法模式来出场了!! 
四、工厂方法模式 
先来看下它的组成吧: 
1、抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。 
2、具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。 
3、抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。 
4、具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。 
来用类图来清晰的表示下的它们之间的关系: 
我们还是老规矩使用一个完整的例子来看看工厂模式各个角色之间是如何来协调的。话说暴发户生意越做越大,自己的爱车也越来越多。这可苦了那位司机师傅了,什么车它都要记得,维护,都要经过他来使用!于是暴发户同情他说:看你跟我这么多年的份上,以后你不用这么辛苦了,我给你分配几个人手,你只管管好他们就行了!于是,工厂方法模式的管理出现了。代码如下: 
//抽象产品角色,具体产品角色与简单工厂模式类似,只是变得复杂了些,这里略。 
//抽象工厂角色 
public interface Driver{ 
public Car driverCar(); 
public class BenzDriver implements Driver{ 
public Car driverCar(){ 
return new Benz(); 
public class BmwDriver implements Driver{ 
public Car driverCar() { 
return new Bmw(); 
......//应该和具体产品形成对应关系,这里略... 
//有请暴发户先生 
public class Magnate 
public static void main(String[] args) 
try{ 
Driver driver = new BenzDriver(); 
Car car = driver.driverCar(); 
car.drive(); 
}catch(Exception e) 
{ } 
工厂方法使用一个抽象工厂角色作为核心来代替在简单工厂模式中使用具体类作为核心。让我们来看看工厂方法模式给我们带来了什么?使用开闭原则来分析下工厂方法模式。当有新的产品(即暴发户的汽车)产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。看来,工厂方法模式是完全符合开闭原则的! 
使用工厂方法模式足以应付我们可能遇到的大部分业务需求。但是当产品种类非常多时,就会出现大量的与之对应的工厂类,这不应该是我们所希望的。所以我建议在这种情况下使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现。 
当然特殊的情况,就要特殊对待了:对于系统中存在不同的产品树,而且产品树上存在产品族,那么这种情况下就可能可以使用抽象工厂模式了。 
五、小结 
让我们来看看简单工厂模式、工厂方法模式给我们的启迪: 
如果不使用工厂模式来实现我们的例子,也许代码会减少很多--只需要实现已有的车,不使用多态。但是在可维护性上,可扩展性上是非常差的(你可以想象一下,添加一辆车后要牵动的类)。因此为了提高扩展性和维护性,多写些代码是值得的。 

回到抽象产品模式的话题上,可以这么说,它和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。而且使用抽象工厂模式还要满足一下条件: 
1.系统中有多个产品族,而系统一次只可能消费其中一族产品 
2.同属于同一个产品族的产品以其使用。 
来看看抽象工厂模式的各个角色(和工厂方法的如出一辙)~


 

分享到:
评论

相关推荐

    C# 设计模式系列教程-抽象工厂模式

     抽象工厂模式为一个产品家族提供了统一的创建接口。当需要这个产品家族的某一系列的时候,可以从抽象工厂中选出相对应的系列来创建一个具体的工厂类别。 2. 抽象工厂模式中的角色  2.1 抽象工厂(Abstract...

    Java 基础核心总结 +经典算法大全.rar

    接口和抽象类接口 抽象类异常 认 识 Exception 什么是 Throwable 常见的 Exception 与 Exception 有关的 Java 关键字 throws 和 throw try 、finally 、catch 什么是 Error 内部类 创建内部类集合 Iterable 接口顶层...

    Java设计模式之工厂模式(Factory)

    二、简介 工厂模式主要是为创建对象提供了接口。工厂模式按照《Java 与模式》中的提法分为...在java 中由接口或者抽象类来实现。 3、具体产品角色:工厂类所创建的对象就是此角色的实例。在java 中由一个具体类实现。

    BaseActivityTitleFactory:本项目是一个Android快速开框架,通过这个项目,起码可以熟练的运用:工厂模式;构建者模式;单例模式;适配器模式;接口、抽象类、回调

    接口、抽象类、回调 在我看来,可复用的模块不止功能类,也可以是页面的布局元素,比如弹窗、标题栏、按钮等元素,由于是一个公司出品的app,所以整体风格元素都差不多,所以这些元素都是可以复用的。 在这个项目里...

    .NET工厂方法模式讲解

    核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。 工厂方法...

    从模式讲到设计模式再到面向对象设计模式

    问题来了,电脑中的显示器(也就是抽象工厂中的抽象产品)有没有公有接口或者说是方法呢?一定是有的,比如显示器都可以显示吧,都有一个数据线与电脑提供的接口相连接吧等等。这也就要求联想在生产自己的显示器的...

    基于模板方法与抽象工厂的复合模式

    因此将抽象工厂模式嵌入到模板方法模式形成一个复合模式, 复合模式的设计核心是为每一个延迟到子类的可变的步骤提供一个创建对象的接口, 该接口对一个完整的产品族进行了定义. 复合模式既保证了算法结构的稳定性, ...

    实例讲解Python设计模式编程之工厂方法模式的使用

    工厂方法模式中的核心工厂类经过功能抽象之后,成为了一个抽象的工厂角色,仅负责给出具体工厂子类必须实现的接口,而不涉及哪种产品类应当被实例化这一细节。工厂方法模式的一般性结构如下图所示,图中为了简化只给...

    Simple Factory Pattern.rar【GoF的简单工厂模式(C#源码)】

    * 抽象产品(Product)角色:是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。 * 具体产品(Concrete Product)角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个...

    工厂模式--简单工厂模式

    Simple Factory Pattern ...抽象产品角色:是简单工厂模式所创建的所有对象父类,他负责描述所有实例所共有的公共接口 具体产品角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例.

    设计模式(四)——工厂模式

    工厂模式 意义 工厂模式 实现了创建者和调用者的分离。...DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程(不要让类继承具体类,而是继承抽象类或者是实现 i

    Java设计模式——工厂设计模式

    抽象工厂模式 面向对象设计的基本原则: OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。 DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对...

    JAVA面向对象详细资料

    28.4 抽象方法与抽象类的关系 42 28.5 抽象方法与抽象类的使用 42 28.6 abstract的使用场合 42 29 练习:写一个“愤怒的小鸟”: 43 30 final关键字 43 30.1 final可以修饰到3个地方 43 30.2 引用类型加final修饰...

    Java_Factory.rar_factory_java facto_java facto_java factory

    核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

    java简单工厂设计模式.docx

    模式中包含的角色和职责 ...2.抽象(Product)角色,简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。 3.具体产品(Concrete Product)角色简单工厂模式所创建的具体实例对象

    工厂模式在Zend Framework中应用介绍

    首先我们先引用些概念: 工厂模式:专门定义一个类来负责... 抽象产品(Product)角色:是工厂模式所创建所有对象的父类,它负责描述所有实例所共有的公共接口。 具体产品(Concrete Product)角色:是工厂模式的创建

    MF00639-.NET Core前后端分离快速开发框架.zip

    数据库:支持SqlServer,PostgreSQL,MySQL,Oracle(框架使用简单工厂,工厂方法,抽象工厂,可轻松更换数据库),Redis作为分布式缓存 前端:Vue2.x全家桶+Ant Design Vue,其中集成常用组件,力求方便项目开发。 ...

    二十三种设计模式【PDF版】

    经常以那些技术只适合大型项目为由,避开或忽略它们,实际中,Java 的接口或抽象类是真正体现 Java 思想的核心所在,这些 你都将在 GoF 的设计模式里领略到它们变幻无穷的魔力。 GoF 的设计模式表面上好象也是一种...

    java 面试题 总结

    接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类...

    23种设计模式.txt

     工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。  4、...

Global site tag (gtag.js) - Google Analytics