分层与接口

Posted 炼金士

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分层与接口相关的知识,希望对你有一定的参考价值。

最近浏览了一些网上对分层 、接口等概念的讨论。

想想这个问题也是我所接触到的一些团队,他们所一直困扰的问题,我觉得有必要整理一下思路。

这里所说的分层,是逻辑上的概念,和物理分层不搭界,也就是说,如果你的程序不做分布式,按照分层的设计思路,你也应该在你的代码级别上体现出逻辑分层的思想。

在CSLA里,一个业务对象自身就包含了持久层和逻辑层(CSLA里要细分层次的话,数下来蛮多的:UI、UI与客户端业务逻辑层的工作间层、客户端业务逻辑层、客户端与服务端的物理协议层、服务端业务逻辑层、服务端业务逻辑层与持久层的协议层、持久层、数据库,当然,开发人员的工作是写其中的客户端业务逻辑层、服务端业务逻辑层、持久层,其余CSLA基本已经帮你做好了)。在这些业务对象里,CSLA里是用模板的方式提供给你,使得你方便地在这些对象里实现了除UI之外其余的层。

如果你将物理分层和逻辑上的分层相混搅的话,会感觉CSLA实在是瞎胡闹,怎么能将几层的代码写在一个类里面呢。

如果你搞懂并遵循了CSLA给你提供的模板和框架,那么,设计、开发和后期的维护都是比较方便的,特别是维护,你知道问题会处在哪里,可以在哪个类的哪个代码块里找到。

说具体点,如果数据库某个表增加了一个字段,那么,系统要用到这个字段的话,业务逻辑层必然要做改动,相关的业务类要增加属性,以及对这些属性的操作函数,然后体现到UI上。这就是我们所说的,业务的改变,会从UI到后台的整个的修改,这是无法规避的事实,没有一个现成的方法能解决掉这个问题,你必须从UI改到后台。

这个问题的关键是,接口改变了!

分层的好处,是建立在接口稳定的基础之上的。在接口不变的状况下,每个层可以单独去修改自己的代码,这方便了团队的合作开发,只要大家确定好接口。

但如果是上面说的要加一个字段,并且要体现到UI层,你肯定要到每个层里去修改相应的代码,而对于接口,或者增加、或者修改。不管如何,接口是动了。

有人会想到这样的办法来解决问题:函数名和参数都是固定的,然后,用一个自定义格式的合并字符串来提交数据和返回数据。例如像下面的案例:

存储过程这么写:

CREATE or REPLACE PROCEDURE TEST_P(
  I_PARM IN LONG,
  I_VALUE IN LONG,
  O_PARM OUT LONG,
  O_VALUE OUT LONG)
IS
BEGIN
  O_PARM := I_PARM;
  O_VALUE := I_VALUE;
END TEST_P;

I_PARM和I_VALUE是输入的参数集合,I_PARM里用#2分隔参数名,I_VALUE里用#2分隔参数值;

O_PARM和O_VALUE是输出的参数集合,O_PARM里用#2分隔参数名,O_VALUE里用#2分隔参数值;

那么调用端,怎么应付这种协议呢?(注意,我这里说的是协议,不是接口)

procedure ThcRenovateSocketConnection.ExecProc(const StoredProcName: string;
  const InParamNames: Variant; const InParamValues: Variant;
  var OutParamNames: Variant; var OutParamValues: Variant);
var
  Params: TParams;
  S: string;
  I: Integer;
begin
  if VarIsArray(InParamNames) and VarIsArray(InParamValues) and
    (VarArrayDimCount(InParamNames) <> VarArrayDimCount(InParamValues)) then
    raise Exception.Create('参数 InParamNames 和 InParamValues 的数据项不对称!');
  Params := TParams.Create;
  try
    S := '';
    if VarIsArray(InParamNames) and (VarArrayDimCount(InParamNames) = 1) then
      for I := VarArrayLowBound(InParamNames, 1) to VarArrayHighBound(InParamNames, 1) do
        S := S + InParamNames[I] + #2
    else
      S := VarToStr(InParamNames) + #2;
    Params.CreateParam(ftString,  ’I_PARM‘, ptInput).AsString := S;
    S := '';
    if VarIsArray(InParamValues) and (VarArrayDimCount(InParamValues) = 1) then
      for I := VarArrayLowBound(InParamValues, 1) to VarArrayHighBound(InParamValues, 1) do
        S := S + InParamValues[I] + #2
    else
      S := VarToStr(InParamValues) + #2;
    Params.CreateParam(ftString, 'I_VALUE', ptInput).AsString := S;
    Params.CreateParam(ftString, 'O_PARM‘, ptOutput).AsString := ' ';
    Params.CreateParam(ftString, 'O_VALUE', ptOutput).AsString := ' ';
    ExecProc(StoredProcName, Params);
    with TStringList.Create do
    try
      S := StringReplace(StringReplace(VarToStr(Params.ParamValues['O_PARM’]), #$D, #$1D, [rfReplaceAll]), #$A, #$1A, [rfReplaceAll]);
      Text := StringReplace(S, #2, #$D#$A, [rfReplaceAll]);
      if Count = 1 then
        OutParamNames := StringReplace(StringReplace(Strings[0], #$1D, #$D, [rfReplaceAll]), #$1A, #$A, [rfReplaceAll])
      else
      begin
        OutParamNames := VarArrayCreate([0, Count - 1], varVariant);
        for I := 0 to Count - 1 do
          OutParamNames[I] := StringReplace(StringReplace(Strings[I], #$1D, #$D, [rfReplaceAll]), #$1A, #$A, [rfReplaceAll]);
      end;
      S := StringReplace(StringReplace(VarToStr(Params.ParamValues['O_VALUE']), #$D, #$1D, [rfReplaceAll]), #$A, #$1A, [rfReplaceAll]);
      Text := StringReplace(S, #2, #$D#$A, [rfReplaceAll]);
      if Count = 1 then
        OutParamValues := StringReplace(StringReplace(Strings[0], #$1D, #$D, [rfReplaceAll]), #$1A, #$A, [rfReplaceAll])
      else
      begin
        OutParamValues := VarArrayCreate([0, Count - 1], varVariant);
        for I := 0 to Count - 1 do
          OutParamValues[I] := StringReplace(StringReplace(Strings[I], #$1D, #$D, [rfReplaceAll]), #$1A, #$A, [rfReplaceAll]);
      end;
    finally
      Free;
    end;
  finally
    Params.Free;
  end;
  if VarIsArray(OutParamNames) and VarIsArray(OutParamValues) and
    (VarArrayDimCount(OutParamNames) <> VarArrayDimCount(OutParamValues)) then
    raise Exception.Create('参数 OutParamNames 和 OutParamValue 的数据项不对称!');
end;

这种方法,在存储过程和调动端,都得写一个解析和打包的协议层,但能否解决上面所说的问题呢?

不能。

因为,增加了一个字段,必然要在服务和调用者双方的代码里修改对应的代码,你不改服务代码或者不改调用者代码,试试看,系统还是旧的。

上面的方法,是混搅了协议和接口的概念,不管你怎么改变协议和调用方法,但接口还是接口。

接口定义是逻辑上的,它隐藏在了代码里。

所以,我更倾向于将接口做成显式的,也就是强类型的,一旦有误,在编译期就通不过。

所以上面的方法,解决不了接口变化的情况,干脆,按照正规的方法写存储过程的参数,一个个地罗列出来。当然,为了升级方便,服务方可以提供新的接口(新的存储过程,原代码拷进来进行修改),然后,当调用方也升级成功了,再删除旧的存储过程。

所以,不管层之间是传递数据包、对象、还是其他自定义的东西,那都是协议,不是接口。接口是函数的参数、类的属性。。。,怎样的千变万化,只是形式(协议)上不同而已,它仍然存在在你的代码里,它动了,所串起来的东西都得动。

所以,我们要解决的是:如何在接口变化的情况下,能很快的处理问题。

最不好的情况是,一个点动了,拎起无数条线索,那就有得忙乎了,这就是所谓的松耦合的概念。

特别是一些业务需求经常变化的项目尤其重要,接口设计的很差的系统是没法维护下去的,甚至都没法上线。

除了系统设计,遵循松耦合的思想和准则外(达到目的是,一个点动了,拎起来的线索是有限和可控的),需要一个好的框架来配合。

一个好的框架,就是让你方便地管理接口,你可以很快找到地方,去修改相应的代码,而且在编译期就可以把问题暴露出来。

CSLA所谓在层间传输对象,主要是指它在物理层上进行传输所带来的优点(这个优点有点保留)。

CSLA反而是在逻辑的层上,进行了统一管理,你可以在一个类里面完成各个逻辑层的修改,这不是显而易见的好处吗!

这是非常值得借鉴的。

以上是关于分层与接口的主要内容,如果未能解决你的问题,请参考以下文章

C# 使用接口做到界面与后台操作分层

王道考研计算机网络—分层结构 协议 接口 服务

王道考研计算机网络—分层结构 协议 接口 服务

Dubbo 的整体架构设计有哪些分层?

项目分层和解析

常用接口分类与模块设计的方法