`
TonyLian
  • 浏览: 401449 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

我与OO老师的问答(SSH与OO可以兼得吗),邀你继续...

阅读更多
问:面向对象的设计、开发 与 实际工作中的规范化、流程化、定型化 架构之间的矛盾,如何处理?如何使OOA、OOD实战化,特别是在水平各异的整个团队中普遍展开

答:规范化、流程化、定型化与面向对象的设计、开发没有绝对矛盾。开发规范中文书中都把UML的使用模板化了,反而更利于面向对象的设计、开发。或许面向对象更适合迭代式开发,但是瀑布似的规范化、流程化、定型化一样可以使用面向对象。在团队中,水平最高的架构师做OOA,设立整体的规范模板,次之的做OOD,再次之的做OOP,水平各异的团队要保证高水平对低水平人员的足够的review

问:开发规范文书中所定义的模板化的东西,都是共性的东西,是“放之四海而皆准”的东西,它不可能知道我要做的一个项目的其中一个业务是什么样子的。
也就是说,它仅仅能做到框架结构是OO的。我的问题是,如何才能让业务也OO起来呢?比如,课上例子中办理旅游申请这个业务,把申请单和申请人都作成相应的Class。而到了另外一个页面,比如导游回来后要报销,那么报销的科目可能是一个Class。这时候,没有哪个文书或生成工具可以帮助我们。反而,如果我们按照课程中教授的UML方法去给这些业务建模的话,得到的那些Class,又当如何安放到我们的框架规范当中去呢?

答:业务的OO,要在问题域,也就是业务的现实世界模拟抽象出概念模型来,这个概念模型,也就是有概念类组成的,与现实世界的业务是能够一一映射的,有了这个模型,我们就可以OO下去。

问:一个成型的框架,比如 MVC,比如3层结构,已经规定好了,数据通过POJO保存,业务逻辑在Service中,这样Service也好,数据持久层的DAO也好,都成了贫血对象,在有些架构中它们甚至是单例的。POJO是它们之间调用时的参数或返回值。
于是,从结构来看,3层之间各司其责,互相关联;不同POJO也可能有聚合甚至组合关系,这是OO呀。
但是,从业务角度看,通过参数传递数据,通过函数进行运算,这不就是彻头彻尾的PO吗?
所以,“我们就可以OO下去”,到底该如何去做呢?

答:其实我们做一个业务,没有必要彻头彻尾的OO,业务的概念模型OO了,依照成熟的框架把各层的接口设计的OO了,这样就够了。至于具体到每个层次的某一模块,我完全可以是PO的思维来实现的。关键是现实的业务到概念模型的OO映射,概念模型到设计模型的OO映射。到具体的细节实现,不必纠结的

回答到这里,老师将帖子关闭了,但是,我的疑惑才刚刚说到关键的地方。

我还想继续问:那我们上完课,再遇到一个类似 旅行社OA 的项目,还是没法把课上学到的 建模 理论应用在项目中呀?
我承认,我所指的框架,很大程度上受到了SSH的影响。或者说在SSH里,业务如何OO起来?
我想也就是JE里才能看到更好,更能眼前一亮的答案。
分享到:
评论
41 楼 treblesoftware 2011-01-15  
gdpglc 写道
zhmiao 写道
搞这么抽象的东西,代码写出来可维护性强就OK了。

从用户需求到可维护的代码,中间是有一段距离的。没有软件设计方法论的指导,靠灵感得到可维护的代码,是不靠谱的。


笑死我了,你是没遇见一些软件模型,有些业务使用DDD或者充血模型这些浮云的东西根本无法解决,那时你才知道,在这里夸死OO是多么的幼稚和可笑,如果用OO搭建一个DEMO还可以,神论也就是这么出来的。

不同的模型和架构采用不同的软件设计方法才是真正可行的,系统才会变得清晰可见,没有一种方法可以完全满足各种模型,因为现在的软件平台和思想决定了这一切。
40 楼 gdpglc 2011-01-15  
zhmiao 写道
搞这么抽象的东西,代码写出来可维护性强就OK了。

从用户需求到可维护的代码,中间是有一段距离的。没有软件设计方法论的指导,靠灵感得到可维护的代码,是不靠谱的。
39 楼 H_eaven 2011-01-14  
leexhwhy 写道
peterwei 写道
gdpglc 写道
没人说一定要用。而且现在大部份人都没用。因为它难用,而且大部份人用的是Spring。
好处坏处看经典吧,我说不出新东西。

你这个例子,好象不明显,而且我也不太理解你的需求。我举个别的吧,比如转帐。

从 A帐户取transferNum到B。

以下代码,只表语意

面向过程的方式:
//transaction begin
Account a=AccountDao.find......
a.setBalance(a.getBalance-transferNum);
AccountDao.save(a);
Account b=AccountDao.find......
b.setBalance(b.getBalance+transferNum);
AccountDao.save(b);
//transaction end

面向对象的方式:
Account类提供如下方法:
void transferTo(Accout a,transferNum){
    this.balance-=transferNum;
    a.balance+=transferNum;
}

然后Service 里这样用:
//事务开始
   Account a=获得Account
   Acconnt b=获得Account
   a.transferTo(b,50);
//事务结束




ok,我终于理解你的意思了。说得那么绕。哈哈。你直接说充血,就是在Domain Object里把逻辑操作和数据都封装在一起。但这种做法,我是持反对观点的,也不提倡。
action-->service(什么也不做,只管事务之类)-->domain object(里面有操作和属性)--dao
没有疑问了。这样的缺点我说说:
1.domain object和dao形成了双向依赖,复杂的双向依赖有可能产生很多潜在的问题。
2.你这样做会让大家产生很在的误会,导致整个结构的混乱无序。这个是service呢还是domain object呢?
3、service层必须对所有的domain object的业务逻辑提供相应的事务封装方法,太重复了,看着就不爽。比如你service里肯定还有个和object类似的方法transfer。何必多此一举。直接贫血不就行了,这样pojo还简单些。


我的结论,你何必一定要这种充血oo方式,难道就为了完全的oo?java里贫血不挺好的嘛,大家都这么用。



1,DDD的充血模型就一定会出现domain object和dao的双向依赖?不一定吧。
2,团队对DDD的理解确实会出现一些混淆,但是学习任何技术都是需要付出代价的。
3:service不一定出现和domain object对应的方法吧,可以参考DCI理论中动态织入或者事件的机制吧。

个人观点,java开发中使用spring,想充血确实很难,关键是对象的状态怎么维护和控制。向EJB似的搞个容器?太重了吧。并且面向过程也没有什么错吧,至少这么多项目都是这么做的。
所以针对楼主的疑惑,框架级别还是要OO的,业务分析设计也是需要OO的。复杂的业务场景也是需要OO的。至于Service,pojo,dao,action等等,不要管他是OO还是面向过程。just do it!
这个只是个伪劣的OO吧,就不要往OO上靠了吧.

38 楼 zhmiao 2011-01-14  
搞这么抽象的东西,代码写出来可维护性强就OK了。
37 楼 魔力猫咪 2011-01-14  
建议还是多看一下领域驱动设计方面的书籍。充分理解一下应用层和领域层的划分,服务、领域对象和值对象的关系。
Spring也不是没有充血模型的,Spring Roo就是充血的。
36 楼 leexhwhy 2011-01-14  
peterwei 写道
gdpglc 写道
没人说一定要用。而且现在大部份人都没用。因为它难用,而且大部份人用的是Spring。
好处坏处看经典吧,我说不出新东西。

你这个例子,好象不明显,而且我也不太理解你的需求。我举个别的吧,比如转帐。

从 A帐户取transferNum到B。

以下代码,只表语意

面向过程的方式:
//transaction begin
Account a=AccountDao.find......
a.setBalance(a.getBalance-transferNum);
AccountDao.save(a);
Account b=AccountDao.find......
b.setBalance(b.getBalance+transferNum);
AccountDao.save(b);
//transaction end

面向对象的方式:
Account类提供如下方法:
void transferTo(Accout a,transferNum){
    this.balance-=transferNum;
    a.balance+=transferNum;
}

然后Service 里这样用:
//事务开始
   Account a=获得Account
   Acconnt b=获得Account
   a.transferTo(b,50);
//事务结束




ok,我终于理解你的意思了。说得那么绕。哈哈。你直接说充血,就是在Domain Object里把逻辑操作和数据都封装在一起。但这种做法,我是持反对观点的,也不提倡。
action-->service(什么也不做,只管事务之类)-->domain object(里面有操作和属性)--dao
没有疑问了。这样的缺点我说说:
1.domain object和dao形成了双向依赖,复杂的双向依赖有可能产生很多潜在的问题。
2.你这样做会让大家产生很在的误会,导致整个结构的混乱无序。这个是service呢还是domain object呢?
3、service层必须对所有的domain object的业务逻辑提供相应的事务封装方法,太重复了,看着就不爽。比如你service里肯定还有个和object类似的方法transfer。何必多此一举。直接贫血不就行了,这样pojo还简单些。


我的结论,你何必一定要这种充血oo方式,难道就为了完全的oo?java里贫血不挺好的嘛,大家都这么用。



1,DDD的充血模型就一定会出现domain object和dao的双向依赖?不一定吧。
2,团队对DDD的理解确实会出现一些混淆,但是学习任何技术都是需要付出代价的。
3:service不一定出现和domain object对应的方法吧,可以参考DCI理论中动态织入或者事件的机制吧。

个人观点,java开发中使用spring,想充血确实很难,关键是对象的状态怎么维护和控制。向EJB似的搞个容器?太重了吧。并且面向过程也没有什么错吧,至少这么多项目都是这么做的。
所以针对楼主的疑惑,框架级别还是要OO的,业务分析设计也是需要OO的。复杂的业务场景也是需要OO的。至于Service,pojo,dao,action等等,不要管他是OO还是面向过程。just do it!

35 楼 hatedance 2011-01-14  
<p>Spring和DDD是没有必然联系的。</p>
<p>不会DDD,照样拿来spring,分3层进行web开发。</p>
<p>但是如果你学习了DDD,结合spring,也挺好的,就是你所谓的OO,所谓的非贫血。</p>
34 楼 wu_quanyin 2011-01-14  
怎么看怎么像做学术论文似的。。。
33 楼 TonyLian 2011-01-14  
感谢各位的讨论,受益匪浅。
尤其是 gdpglc peterwei 两位,虽然昨晚我没能上线,但是今天一早就搬板凳听两位讲课。gdpglc 的那段分别 PO和OO 的代码,其实正是我想问的问题。
让大家见笑了~
32 楼 Frankie199 2011-01-14  
你是在怀疑老师,还是怀疑你自己,呵呵
31 楼 liujickson 2011-01-14  
TonyLian 写道
问:面向对象的设计、开发 与 实际工作中的规范化、流程化、定型化 架构之间的矛盾,如何处理?如何使OOA、OOD实战化,特别是在水平各异的整个团队中普遍展开

答:规范化、流程化、定型化与面向对象的设计、开发没有绝对矛盾。开发规范中文书中都把UML的使用模板化了,反而更利于面向对象的设计、开发。或许面向对象更适合迭代式开发,但是瀑布似的规范化、流程化、定型化一样可以使用面向对象。在团队中,水平最高的架构师做OOA,设立整体的规范模板,次之的做OOD,再次之的做OOP,水平各异的团队要保证高水平对低水平人员的足够的review

问:开发规范文书中所定义的模板化的东西,都是共性的东西,是“放之四海而皆准”的东西,它不可能知道我要做的一个项目的其中一个业务是什么样子的。
也就是说,它仅仅能做到框架结构是OO的。我的问题是,如何才能让业务也OO起来呢?比如,课上例子中办理旅游申请这个业务,把申请单和申请人都作成相应的Class。而到了另外一个页面,比如导游回来后要报销,那么报销的科目可能是一个Class。这时候,没有哪个文书或生成工具可以帮助我们。反而,如果我们按照课程中教授的UML方法去给这些业务建模的话,得到的那些Class,又当如何安放到我们的框架规范当中去呢?

答:业务的OO,要在问题域,也就是业务的现实世界模拟抽象出概念模型来,这个概念模型,也就是有概念类组成的,与现实世界的业务是能够一一映射的,有了这个模型,我们就可以OO下去。

问:一个成型的框架,比如 MVC,比如3层结构,已经规定好了,数据通过POJO保存,业务逻辑在Service中,这样Service也好,数据持久层的DAO也好,都成了贫血对象,在有些架构中它们甚至是单例的。POJO是它们之间调用时的参数或返回值。
于是,从结构来看,3层之间各司其责,互相关联;不同POJO也可能有聚合甚至组合关系,这是OO呀。
但是,从业务角度看,通过参数传递数据,通过函数进行运算,这不就是彻头彻尾的PO吗?
所以,“我们就可以OO下去”,到底该如何去做呢?

答:其实我们做一个业务,没有必要彻头彻尾的OO,业务的概念模型OO了,依照成熟的框架把各层的接口设计的OO了,这样就够了。至于具体到每个层次的某一模块,我完全可以是PO的思维来实现的。关键是现实的业务到概念模型的OO映射,概念模型到设计模型的OO映射。到具体的细节实现,不必纠结的

回答到这里,老师将帖子关闭了,但是,我的疑惑才刚刚说到关键的地方。

我还想继续问:那我们上完课,再遇到一个类似 旅行社OA 的项目,还是没法把课上学到的 建模 理论应用在项目中呀?
我承认,我所指的框架,很大程度上受到了SSH的影响。或者说在SSH里,业务如何OO起来?
我想也就是JE里才能看到更好,更能眼前一亮的答案。

30 楼 njyu 2011-01-13  
说和做两回事
29 楼 treblesoftware 2011-01-13  
没看透的人对这些比较有兴趣吧。
28 楼 gdpglc 2011-01-13  
领域对象来自于领域,业务逻辑也来自领域。所以才有 Domain Driven Design的理论。
27 楼 seele 2011-01-13  
gdpglc 写道
没人说一定要用。而且现在大部份人都没用。因为它难用,而且大部份人用的是Spring。
好处坏处看经典吧,我说不出新东西。

你这个例子,好象不明显,而且我也不太理解你的需求。我举个别的吧,比如转帐。

从 A帐户取transferNum到B。

以下代码,只表语意

面向过程的方式:
//transaction begin
Account a=AccountDao.find......
a.setBalance(a.getBalance-transferNum);
AccountDao.save(a);
Account b=AccountDao.find......
b.setBalance(b.getBalance+transferNum); 业务
AccountDao.save(b); 持久化
//transaction end

面向对象的方式:
Account类提供如下方法:
void transferTo(Accout a,transferNum){
    this.balance-=transferNum;
    a.balance+=transferNum;
}

然后Service 里这样用:
//事务开始
   Account a=获得Account
   Acconnt b=获得Account
   a.transferTo(b,50);
//事务结束






混着写

Account 是个实体,本身的业务是由人给予它含义的,所以不建议放到属于自己的类里实现transferTo
26 楼 seele 2011-01-13  
gdpglc 写道
TonyLian 写道
感觉我的问题是具体的,而老师的回答都是“太极”的。

而这么一个具体的问题不弄清楚,如何能练就“太极”!?

在SSH框架下,开发一个OA(先不上升到工作流)系统,如何OO的把,人员、事宜等都落实?

在UML工具中画的 类图 如何变为现实的代码?如果用工具直接生成代码,如何放到SSH当中去呢?

所谓让业务逻缉OO,我想就是充血模型。引用一下Robin的话:

robin 写道
如果你用的是Spring,没啥说的,必须贫血,你想充血也充不起来;
如果你用的是RoR,也没啥说的,直接充血,你想贫血也未必贫得下来;



实际上,让业务逻缉OO,对于SSH是有困难的。

现在的项目在实现时,业务逻缉主要都是以面向过程的方式表达的。

而且,面向过程的表达方式,是有其优点的。最主要的就是很容易被大众接受和理解。我觉得你问的问题很好,我刚工作时,也一直纠结在这个问题上。后来终于明白,其实大家都在面向过程开发...


实际上,让业务逻缉OO,对于SSH是有困难的。
不是让业务逻辑OO,而是在实现业务逻辑的时候保持业务对象的OO
比如逻辑层写查询用户
会是***Mgr.queryUser(String userName,String passWord);
还是***Mgr.quertUser(User user);


现在的项目在实现时,业务逻缉主要都是以面向过程的方式表达的。
逻辑必然是过程,不是过程话的语言,能表达清楚吗?

很反感这样的方式...把request丢到Manager里
	
public ModelAndView queryHistory(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		HashMap<String,Object> returnMap = ***Manager.query***List(request);
		return new ModelAndView("ajax_***", returnMap);
	} 


25 楼 gdpglc 2011-01-13  
那问题不就明白了:

实际上SSH里的业务逻缉表达是面向过程的,也就是贫血。

到于 充血 vs 贫血 的问题,我也还是小学生,暂不发表相关言论。
24 楼 peterwei 2011-01-13  
76052186 写道
很乱,不知道这说啥。
说我的看法,楼主的意思是想问,框架怎么才能与业务模型相互OO.

1:没有哪个软件是完全OO的,总有一些不是。比如Service在于给业务模型发消息时。保存数据到数据库时。
2:借助spring,hiberante等框架没办法很好的实现充血模型,这个讨论很久了。

楼主一直没把问题说清楚。我和gdpglc讨论就好了,不管他了。哈哈。
23 楼 peterwei 2011-01-13  
gdpglc 写道
没人说一定要用。而且现在大部份人都没用。因为它难用,而且大部份人用的是Spring。
好处坏处看经典吧,我说不出新东西。

你这个例子,好象不明显,而且我也不太理解你的需求。我举个别的吧,比如转帐。

从 A帐户取transferNum到B。

以下代码,只表语意

面向过程的方式:
//transaction begin
Account a=AccountDao.find......
a.setBalance(a.getBalance-transferNum);
AccountDao.save(a);
Account b=AccountDao.find......
b.setBalance(b.getBalance+transferNum);
AccountDao.save(b);
//transaction end

面向对象的方式:
Account类提供如下方法:
void transferTo(Accout a,transferNum){
    this.balance-=transferNum;
    a.balance+=transferNum;
}

然后Service 里这样用:
//事务开始
   Account a=获得Account
   Acconnt b=获得Account
   a.transferTo(b,50);
//事务结束




ok,我终于理解你的意思了。说得那么绕。哈哈。你直接说充血,就是在Domain Object里把逻辑操作和数据都封装在一起。但这种做法,我是持反对观点的,也不提倡。
action-->service(什么也不做,只管事务之类)-->domain object(里面有操作和属性)--dao
没有疑问了。这样的缺点我说说:
1.domain object和dao形成了双向依赖,复杂的双向依赖有可能产生很多潜在的问题。
2.你这样做会让大家产生很在的误会,导致整个结构的混乱无序。这个是service呢还是domain object呢?
3、service层必须对所有的domain object的业务逻辑提供相应的事务封装方法,太重复了,看着就不爽。比如你service里肯定还有个和object类似的方法transfer。何必多此一举。直接贫血不就行了,这样pojo还简单些。


我的结论,你何必一定要这种充血oo方式,难道就为了完全的oo?java里贫血不挺好的嘛,大家都这么用。
22 楼 76052186 2011-01-13  
很乱,不知道这说啥。
说我的看法,楼主的意思是想问,框架怎么才能与业务模型相互OO.

1:没有哪个软件是完全OO的,总有一些不是。比如Service在于给业务模型发消息时。保存数据到数据库时。
2:借助spring,hiberante等框架没办法很好的实现充血模型,这个讨论很久了。

相关推荐

    [最强数据恢复软件].OO.DiskRecovery.v6.0.6298.x86.x64

    [最强数据恢复软件].OO.DiskRecovery.v6.0.6298.x86.x64

    go.oo声音模块

    GO.OO声音模块。。。很好用的。....

    GO.OO声音模块

    GO.OO声音模块

    OO.Defrag.Professional.v16.0.135.Keygen-MESMERiZE

    OO.Defrag.Professional.v16.0.135.Keygen-MESMERiZE

    [最强数据恢复系统.O&O.DiskRecovery.v6.0.6298.].OO.DiskRecovery.v6.0.6298.x86.x64

    [最强数据恢复系统.O&O.DiskRecovery.v6.0.6298.].OO.DiskRecovery.v6.0.6298.x86.x64

    SAP OO ALV技术介绍.pdf

    OO ALV技术可以满足大多数ALV需求,但有时需要与后续的屏幕开发等集中在一个屏幕中,或者需要实现一些函数ALV不可实现的事件等。 OO ALV技术的实现方式是通过调用cl_gui_alv_grid类的方法set_table_for_first_...

    oo2core_6_win64

    直接拷贝该文件到系统目录里:  1、Windows 95/98/Me...4、如果您的系统是64位的请将32位的dll文件复制到C:\Windows\SysWOW64目录,具体的方法可以参考这篇文章:win7 64位旗舰版系统运行regsvr32.exe提示版本不兼容

    com.yeke.oo_4.9.apk

    com.yeke.oo_4.9.apk

    SAP ABAP开发学习——第10课:OOALV(视频教程)

    10. **集成到SAP GUI**:OOALV不仅可以独立使用,还可以与SAP GUI的其他组件结合,如对话框、表单等,为用户提供统一的界面体验。 通过本课程的学习,你将掌握如何利用OOALV来创建功能强大的数据展示界面,提高SAP...

    偶偶网春节祝福,春节祝福页面oo25.cn网

    【标题与描述解析】 标题"偶偶网春节祝福,春节祝福页面oo25.cn网"表明这是一个关于春节期间,偶偶网推出的祝福页面。偶偶网可能是一个提供在线服务的平台,而oo25.cn可能是该网站的特定子域名,专门用于展示春节...

    [专业磁盘碎片整理工具].OO.Defrag.Profession

    [专业磁盘碎片整理工具].OO.Defrag.Professional.v15.0.73.Incl.Keymaker-ZWT.zip

    SAP OO ALV技术介绍.pptx

    同时,OO ALV技术也可以与标准函数REUSE_ALV_GRID_DISPLAY和REUSE_ALV_GRID_DISPLAY_LVC结合使用,提供更多的报表解决方案。 在实现OO ALV技术时,需要注意以下几点: 1. 需要画一个屏幕,在屏幕上画一个容器(即...

    mmoo.zip_effective bandwidth_mmoo

    在本案例中,我们关注的是"mmoo.zip_effective_bandwidth_mmoo",这是一个与MATLAB相关的项目,用于仿真业务源mmoo的有效带宽。MATLAB是一种强大的数学计算软件,广泛应用于科学研究、工程计算以及数据分析等领域。 ...

    vb-oo.rar_oo_vb类

    通过学习这些示例,你可以深入理解VB的面向对象编程,并提升你的编程技能。记得解压文件并逐个查看,尤其是那些以`.bas`或`.frm`为扩展名的文件,它们通常分别代表了VB的模块(Module)和窗体(Form)代码。通过阅读...

    OOALV常用功能完整简例

    在给定的内容部分,我们可以看到一些与OOALV相关的代码片段。这些代码片段涉及了OOALV创建和初始化的过程,以及一些配置字段目录和表格显示设置的实现。例如,“g_alv_grid”是一个OOALV对象的引用类型,而“g_it_...

    ABAP OOALV学习文档

    ### ABAP OOALV 学习文档详析 #### 一、ABAP OOALV 概述 **ABAP OOALV**(Object-Oriented Application List Viewer)是一种用于SAP系统的高级列表显示技术,主要用于生成复杂的报表和列表视图。自R/3 4.6C版本起...

    Data-OO.rar

    《PyQt实战:数据操作与界面交互》 PyQt是一个强大的Python绑定库,它将Qt库集成到Python中,使得开发者能够利用Python的简洁性和Qt的丰富图形用户界面(GUI)功能来创建复杂的桌面应用程序。在"Data-OO.rar"这个...

    实战OO的pdf自留备份

    《实战OO》是一本深入探讨面向对象(Object-Oriented, OO)编程技术的书籍,主要针对软件开发人员,特别是那些关注于软件设计流程和优化的开发者。此书的PDF版本是作者或读者为了个人学习和参考而留存的备份,包含了...

    OO4O简介以及其在VC++中的应用

    ### OO4O简介及其在VC++中的应用 ...它不仅速度快,还支持多种编程语言,尤其是与VC++的集成使得数据库操作变得更加简单高效。对于需要开发高性能Oracle数据库应用的开发者来说,OO4O无疑是一个很好的选择。

    06.MV56.MCM.OO.02.CN.pdf

    根据提供的文件信息,以下是从文件中提取的详细知识点: 1. 标题和描述知识点: 文件标题为“***.pdf”,这表明该文档可能是一个特定型号模块的手册,即MVI56-MCM模块的ControlLogix平台Modbus通讯模块用户手册。...

Global site tag (gtag.js) - Google Analytics