OOC:10 Delegates — Callback Functions

来自 ChinaUnix Wiki

返回

目录

Delegates
Callback Functions


10.1 Callbacks

An object points to its class description. The class description points to all dynamically linked methods applicable to the object. It looks as though we should be ableto ask an object if it can respond to a particular method. In a way this is a safeguard measure: given a dubious object we can check at run time if we are really allowed to apply a specific method to it. If we do not check, the method’s selectorwill certainly check and crash our program if the object cannot respond, i.e., if theobject’s class description does not contain the method.

对象指代是它的类描述。类的描述指对象可用的所有动态链接方法。我们因该能够查询一个对象是否相应特定的方法。一个安全的方式:对于一个不明确的对象能够在运行时检查是否允许对其应用某一个具体的方法。如果我们不检查,方法选择器一定会检查,如果对象不能响应,程序将崩溃,比如,对象的类的描述没有包含我们调用的方法。

Why would we really want to know? We are out of luck if a method must beapplied unconditionally to an object which does not know about it; therefore, thereis no need to check. However, if it makes no difference to our own algorithmwhether or not the method is applied, being able to ask makes for a more forgiving interface.

为什么我们要知道?如果一个方法要无条件的应用到一个我们一无所知的对象将是很不幸。所以,没必要进行对象方法的检查。但是,如果实现一个更泛化的接口,某些方法的实现与否,对于我们的算法是没有影响的。

The situation arises in the context of callback functions. For example, if we aremanaging a window on a display, some inhabitants of the window might want to beinformed when they are about to be covered up, displayed again, changed in size,or destroyed. We can inform our client by calling a function on which we both haveagreed: either the client has given us the name of a function to be called for a particular event, or we have agreed on a specific function name.

这种情况在回调的上下文中发生。例如,如果管理一个显示的窗口,像窗口的覆盖,重新显示,改变大小或者关闭等行为应该是被通知的。我们能够通知客户通过调用一个双方协商过的函数:客户给我们提供要调用函数的名字或我们同意用一个特定的名字。

Registering a callback function, the first technique, looks like the more flexible approach. A client registers functions only for those events which are important from its point of view. Different clients may use different sets of callback functions,and there is no need to observe a common name space. ANSI-C actuallyuses some callback functions: bsearch() and qsort() receive the comparison functionrelative to which they search and sort and atexit() registers functions to becalled just before a program terminates.

注册一个回调函数,第一个技术,像是最灵活的途径。客户仅注册那些它的角度看来比较重要的事件。不同的客户可能用不同的回调函数集合,而且没有必要用一个公共的名字空间。ANSI-C实际使用了一些回调函数:bsearch()和qsort()接收比较函数——根据它们进行搜索和排序;注册函数atexit()仅在一个程序结束以前被调用。

Agreeing on specific function names looks even easier: a recognizer generatedby lex will call a function yywrap() at the end of an input file and it will continue processing if this function does not return zero. Of course, this is impractical if weneed more than one such function in a program. If bsearch() assumed its comparison function to be called cmp, it would be much less flexible.

在一个具体的函数名字上达成协议似乎更容易。一个由lex程序生成的一个识别器会在一个输入文件的末尾调用函数yywarp(),如果此函数不返回0,则继续处理。当然,如果在一个程序中我们需要不止一个这样的函数是不现实的。如果假定bsearch()的对比函数确定叫做cmp,那么此实现将是不够灵活。

10.2 Abstract Base Classes

Once we look at dynamically linked methods, agreeing on specific method namesfor callback purposes does not seem to be as limiting. A method is called for a particular object, i.e., which code is executed for a callback depends on an object in addition to a specific method name.

一旦我们注意到动态连接的方法,对于具体回调函数名称的协商应该没有问题。为一个特定的对象调用的方法——作为一个回调函数执行,依赖于这个对象和一个具体的方法名。

Methods, however, can only be declared for a class. If we want to communicate with a client in the style of a callback function, we have to postulate anabstract base class with the necessary communication methods and the client object must belong to a subclass to implement these methods. For example:

方法,只有在类中声明。如果我们想以回调函数的方式与客户对象沟通,我们只有在抽象基类中放入必要的通信方法而且作为客户的对象必须是实现了基类中抽象方法的子类的对象。例如:

       % OrderedClass: Class OrderedArray: Object {
       %—
           int cmp (const _self, int a, int b);
           void swap (_self, int a, int b);
       %}

A sorting algorithm can use cmp() to check on two array elements by index, and itcan use swap() to rearrange them if they are out of order. The sorting algorithm can be applied to any subclass of OrderedArray which implements these methods.OrderedArray itself is called an abstract base class because it serves only todeclare the methods; this class should have no objects if the methods are notdefined.

排序算法利用cmp()通过索引比较两个数组元素的大小,如果无序则利用swap()来重新排列顺序。排序算法能被用于实现两个方法的任意的OrderedArray 的类。OrderArray 本身叫做抽象基类,因为它仅仅用来声明方法;如果抽象的方法没有实现这个类应该没有任何对象。

Abstract base classes are quite elegant to encapsulate calling conventions. Forexample, in an operating system there could be an abstract base class for a certain variety of device drivers. The operating system communicates with each driver using the methods of the base class and each driver is expected to implement all ofthese methods to communicate with the actual device.

抽象基类很优雅的封装调用请求。例如:在操作系统里面对于多种不同的设备驱动程序,应该有一个抽象基类。操作系统通过基类中声明的方法,各种驱动程序需要实现所有那些抽象方法,来和实际的设备通信。

The catch is that all methods of an abstract base class must be implemented for the client because they will be called. For a device driver this is perhaps obvious,but a device driver is not exactly a represent ative scenario for callback functions.A window is much more typical: some clients have to worry about exposures and others could not care less — why should they all have to implement all methods?

需要注意的是客户类中基类所有的方法都要被实现,因为它们会被调用。对于一个设备驱动也许这是不大可能的,但是设备驱动并不是确切的表示一个回调函数的应用场景。一个窗口更加典型:一些客户不得不担心内部实现的暴露而且它们要关心——为什么它们要实现所有的方法?

An abstract base class restricts the architecture of a class hierarchy. Without multiple inheritance a client must belong to a particular part of the class tree headedby the abstract base class, regardless of its actual role within an application. As an example, consider a client of a window managing a list of graphical objects. The elegant solution is to let the client belong to a subclass of List but the implementation of a window forces the client to be something like a WindowHandler. As we discussed in section 4.9 we can make an aggregate and let the client contain a Listobject, but then our class hierarchy evolves according to the dictate of the system rather than according to the needs of our application problems.

抽象基类约束了类的层次结构。没有多重继承,一个客户必然属于一以抽象基类开头个类树特定的一部分,不管它在实际应用程序中的角色。一个例子,想一个窗口的客户管理一个图像对象列表,一个优雅的解决方案是客户属于List的子类,但是实现成窗口使其更像一个窗口句柄。像我们在4.9节讨论的那样,我们利用聚合,客户可以包含一个链表对象,那时我们类的结构的进化根据精心设计的系统,而不是根据我们程序问题的实际需要。

Finally, an abstract base class defining callback functions tends to define no private data components for its objects, i.e., the class declares but does not define methods and the objects have no private state. While this is not ruled out by the concept of a class it is certainly not typical and it does suggest that the abstract base class is really just a collection of functions rather than of objects and methods.

最后,抽象的基类趋向定义成一个没有私有数据的组件,然后类声明但是不定义方法而且它们的对象没有私有状态。但是类的概念并没有规定一定要这样,暗示抽象基类仅仅是一个函数的集合而不是对象和方法。

10.3 Delegates

Having made a case against abstract base classes we need to look for a better idea.It takes two to callback: the client object wants to be called and the host does thecalling. Clearly, the client object must identify itself to the host, if it wants the hostto send it a message, but this is all that is required if the host can ask the client what callbacks it is willing to accept, i.e., what methods it can respond to.

已经有一个关于抽象基类的例子我们来看一个更好的想法。它用到两个回调:客户对象需要被调用而主程序进行调用。显然,如果它想主程序给它发调用消息,客户对象必须向主程序标识自己,不过这都需要主调者能询问客户能接受什么样的回调或者说它能响应什么样的方法。

It is significant that our viewpoint has shifted: an object is now part of the callback scenario. We call such an object a delegate. As soon as a delegate announces itself to the host, the host checks what callbacks the delegate can handle and later the host makes precisely those calls which the delegate expects.

很重要的,我们观点提到:某一个对象是回调的上下文环境。只要代理向主调者通告它自己,主调者检查代理能处理什么回调方法,然后主调者精确的进行代理所期望的调用。

As an example we implement a simple framework for a text filter, i.e., a programwhich reads lines from standard input or from files specified as arguments,manipulates them, and writes the results to standard output. As one application welook at a program to count lines and characters in a text file. Here is the main program which can be specified as part of the implementation file Wc.dc:

我们实现了一个文本过滤器的例子,一个程序从标准输入或从以做为参数的文件中读取行,然后操纵它们,并把结果输出到标准输出。作为一个应用我们看一个能计算文本文件中行数和字符个数的程序。下面是我们主程序一部分具体的实现 WC.dc:

      int main (int argc, char * argv [])
      {   void * filter = new(Filter(), new(Wc()));
          return mainLoop(filter, argv);
      }

We create a general object filter and give it as a delegate an application-specific Wc object to count lines and characters. filter receives the arguments of our programand runs the mainLoop() with callbacks to the Wc object.

我们创建一个通用的过滤器对象,给它提供一个具体应用相关的Wc对象来进行行数和单词的计数。过滤器接收程序的参数然后启动主循环,其中包含对Wc对象的对调。

      % WcClass: Class Wc: Object {
          unsigned lines; // lines in current file
          unsigned allLines; // lines in previous files
          unsigned chars; // bytes in current file
          unsigned allChars; // bytes in previous files
          unsigned files; // files completed
      %—
          int wc (_self, const Object @ filter, \
          const char * fnm, char * buf);
          int printFile (_self, const Object @ filter, \
          const char * fnm);
          int printTotal (_self, const Object @ filter);
      %}

The methods in Wc do nothing but line and character counting and reporting the results. wc() is called with a buffer containing one line:

Wc对象中的方法除了对行和字符计数,然后报告结果,什么也不做。wc()调用的时候内部有一个行缓冲区:

      % Wc wc { // (self, filter, fnm, buf)
      %casts
         ++ self —> lines;
         self —> chars += strlen(buf);
         return 0;
      }

Once a single file has been processed, printFile() reports the statistics and adds them to the running total:

每当单独的一个文件被处理完,printFile()函数报告统计结果并且把行的结果加到总数:

       % Wc printFile { // (self, filter, fnm)
       %casts
           if (fnm && strcmp(fnm, "—"))
               printf("%7u %7u %s\n",
                       self —> lines, self —> chars, fnm);
           else
               printf("%7u %7u\n", self —> lines, self —> chars);
           self —> allLines += self —> lines, self —> lines = 0;
           self —> allChars += self —> chars, self —> chars = 0;
           ++ self —> files;
           return 0;
       }

fnm is an argument with the current filename. It can be a null pointer or a minus sign; in this case we do not show a filename in the output.

fnm 是一个带有当前文件名的参数。它可以是以一个空指针或一个减号;这种情况下我们不在输出中显示文件名。

Finally, printTotal() reports the running total if printFile() has been called more than once:

每当单独的一个文件被处理完,printFile()函数报告统计结果并且把行的结果加到总数:

       % Wc printTotal { // (self, filter)
       %casts
           if (self —> files > 1)
           printf("%7u %7u in %u files\n",
           self —> allLines, self —> allChars, self —> files);
           return 0;
       }

Wc only deals with counting. It does not worry about command line arguments, opening or reading files, etc. Filenames are only used to label the output, they have no further significance.

Wc只是计数。不用担心命令行参数,打开或读取文件等等。文件名仅仅用来作为输出的一个标签,没有其他重要的用途。

10.4 An Application Framework — Filter

Processing a command line is a general problem common to all filter programs. Wehave to pick off bundled or separated flags and option values, we must recognize two minus signs ?? as the end of the option list and a single minus sign ? additionally as standard input, and we may need to read standard input or each file argument.Every filter program contains more or less the same code for this purpose,and macros such as MAIN [Sch87, chapter 15] or functions such as getopt(3) helpto maintain standards, but why regurgitate the code in the first place?

对于所有的过滤器程序,处理命令行参数是一个共同的问题。我们不得不挑出捆绑的或分开的标志和选项值,我们必须识别两个减号的--作伪选项列表的结尾,一个减号-作为标准输入,我们需要读标准输入和文件参数。每一个过滤器为了实现此目的或多或少都有相同的代码和宏定义比如:Main[Sch87,15章]或像getopt(3)这样的函数来帮助实现标准,但是为什么要把这段代码放到最重要的位置?

The class Filter is designed as a uniform implementation of command line processingfor all filter programs. It can be called an application framework because it establishes the ground rules and basic structure for a large family of applications.The method mainLoop() contains command line processing once and for all anduses callback functions to let a client deal with the extracted arguments:

类Filter设计作为一个对所有过滤程序适用的统一的命令行处理的实现。它可以称为一个应用框架因为它建立了基本的规则和数据结构满足一大批此类应用。一直让mainLoop()方法包含命令行处理方法利用回调函数让客户来处理提取出来的参数:

       % mainLoop { // (self, argv)
       %casts
           self —> progname = * argv ++;
       while (* argv && ** argv == ’—’)
       {   switch (* ++ * argv) {
           case 0: // single —
               —— * argv; // ... is a filename
               break; // ... and ends options
           case ’—’:
               if (! (* argv)[1]) // two ——
               {   ++ argv; // ... are ignored
                   break; // ... and end options
               }
           default: // rest are bundled flags
               do
                   if (self —> flag)
                   {   self —> argv = argv;
                       self —> flag(self —> delegate,
                       self, ** argv);
                       argv = self —> argv;
                   }
                   else
                   {   fprintf(stderr,
                       "%s: —%c: no flags allowed\n",
                       self —> progname, ** argv);
                       return 1;
                   }
               while (* ++ * argv);
               ++ argv;
               continue;
           }
           break;
       }

The outer loop processes arguments until we reach the null pointer terminating the array argv[] or until an argument does not start with a minus sign. One or two minus signs terminate the outer loop with break statements.

外层循环处理参数,直到遇到数组argv[]中的null指针或遇到一个不以“-”开头的参数。一个或两个“-”用break 语句结束外层循环。

The inner loop passes each character of one argument to the flag-function provided by the delegate. If the delegate decides that a flag introduces an option witha value, the method argval() provides a callback from the delegate to the filter to retrieve the option value:

内循环将参数的每一个字符传给由代理提供的标识函数。如果代理确定一个标志引入一个带值的选项,argval()方法提供一个从代理到过滤器的过滤器的回调来取出选项的值:

       % argval { // (self)
           const char * result;
       %casts
           assert(self —> argv && * self —> argv);
           if ((* self —> argv)[1]) // —fvalue
               result = ++ * self —> argv;
           else if (self —> argv[1]) // —f value
               result = * ++ self —> argv;
           else // no more argument
               result = NULL;
           while ((* self —> argv)[1]) // skip text
               ++ * self —> argv;
           return result;
       }

The option value is either the rest of the flag argument or the next argument if any.self->argv is advanced so that the inner loop of mainLoop() terminates.

选项值既不是剩下的标志参数也不是下一个参数。self->argv 作为其前提,它结束导致mainLoop()中的内循环结束。

Once the options have been picked off the command line, the filename arguments remain. If there are none, a filter program works with standard input. main-Loop() continues as follows:

当参数选项被提取出来,文件名参数。如果什么都没有,过滤器程序接受标准输入。mainLoop()像下面继续运行:

          if (* argv)
          do
              result = doit(self, * argv);
              while (! result && * ++ argv);
          else
              result = doit(self, NULL);
          if (self —> quit)
              result = self —> quit(self —> delegate, self);
          return result;
       }

We let a method doit() take care of a single filename argument. A null pointer represents the situation that there are no arguments. doit() produces an exit code:only if it is zero do we process more arguments.

我们让doit()来处理一个单独的文件名参数。一个空指针代表没有参数。doit()产生一个退出码:只有当其为0的时候我们处理更多参数。

       % doit { // (self, arg)
           FILE * fp;
           int result = 0;
       %casts
           if (self —> name)
               return self —> name(self —> delegate, self, arg);
           if (! arg || strcmp(arg, "—") == 0)
               fp = stdin, clearerr(fp);
           else if (! * arg)
           {   fprintf(stderr, "%s: null filename\n",
                                     self —> progname);
               return 1;
           }
           else if (! (fp = fopen(arg, "r")))
           {   perror(arg);
               return 1;
           }

The client may supply a function to process the filename argument. Otherwise,doit() connects to stdin for a null pointer or a minus sign as an argument; other filenames are opened for reading. Once the file is opened the client can take overwith yet another callback function or doit() allocates a dynamic buffer and startsreading lines:

客户可以提供一个函数来处理文件名参数。另一方面,doit()连接到标准输入当输入为一个空指针或一个“-”;另一个文件被打开用来读取。一旦文件被打开客户能够调用另一个回调函数或doit()动态分配缓冲区并开始读取文件:

   if (self —> file)
       result = self —> file(self —> delegate, self, arg, fp);
   else
   {   if (! self —> buf)
       {   self —> blen = BUFSIZ;
           self —> buf = malloc(self —> blen);
           assert(self —> buf);
       }
       while (fgets(self —> buf, self —> blen, fp))
           if (self —> line && (result =
               self —> line(self —> delegate, self, arg,
                                              self —> buf)))
               break;
       if (self —> wrap)
           result = self —> wrap(self —> delegate, self, arg);
   }
   if (fp != stdin)
       fclose(fp);
   if (fflush(stdout), ferror(stdout))
   {   fprintf(stderr, "%s: output error\n", self —> progname);
       result = 1;
   }
   return result;

}


With two more callback functions the client can receive each text line and perform cleanup actions once the file is complete, respectively. These are the functions that wc uses. doit() recycles the file pointer and checks that the output has been successfullywritten.

用另外两个回调函数客户能接收每一个文本行并且当文件结束的时候做清理工作。那些函数就是wc所使用的函数。doit()回收文件指针并检查输出是否成功的写入。

If a client class implements line-oriented callbacks from the Filter class, itshould be aware of the fact that it deals with text lines. fgets() reads input until its buffer overflows or until a newline character is found. Additional code in doit()extends the dynamic buffer as required, but it only passes the buffer to the client,not a buffer length. fgets() does not return the number of characters read, i.e., ifthere is a null byte in the input, the client has no way to get past it because the nullbyte might actually mark the end of the last buffer of a file with no terminating newline.

如果一个客户类从Fliter类中实现了面向行的回调函数,它将明白如何处理文本行。fgets()一直读到缓冲区满或者遇到一个换行符。doit()中附加的代码扩展了按需动态分配缓冲区,不过它只是传一个缓冲区,没有缓冲区的长度。fgets()不返回读出的字符数,如果在输入中有一个null字符,客户将没有办法越过它,因为null字符事实上标识文件的最后缓冲区的结束。

10.5 The respondsTo Method

How does an object reach its delegate? When a Filter object is constructed it receives the delegate object as an argument. The class description Filter.d defines function types for the possible callback functions and object components to holdthe pointers:

一个对象怎么才能得到它的代理? 当Filter对象创建的时候,接受一个代理对象作为参数。类的描述Filter.d为可能的回调函数定义函数类型和包含指针的对象:

       typedef void (* flagM) (void *, void *, char);
       typedef int (* nameM) (void *, const void *, const char *);
       typedef int (* fileM) (void *, const void *, const char *,
       FILE *);
       typedef int (* lineM) (void *, const void *, const char *,
       char *);
       typedef int (* wrapM) (void *, const void *, const char *);
       typedef int (* quitM) (void *, const void *);
       % Class Filter: Object {
           Object @ delegate;
           flagM flag; // process a flag
           nameM name; // process a filename argument
           fileM file; // process an opened file
           lineM line; // process a line buffer
           wrapM wrap; // done with a file
           quitM quit; // done with all files
           const char * progname; // argv[0]
           char ** argv; // current argument and byte
           char * buf; // dynamic line buffer
           unsigned blen; // current maximum length
       %
           int mainLoop (_self, char ** argv);
           const char * argval (_self);
           const char * progname (const _self);
           int doit (_self, const char * arg);
       %}

Unfortunately, ANSI-C does not permit a typedef to be used to define a function header, but a client class like Wc can still use the function type to make sure its callback function matches the expectations of Filter:

不幸的是,Ansi-C不允许用typedef来定义函数头,但是像Wc这样的类仍然能用函数类型来保证回调函数能匹配Filter所期望的类型:

       #include "Filter.h"
       % Wc wc { // (self, filter, fnm, buf)
       %casts
           assert((lineM) wc == wc);
           ...                 

The assertion is trivially true but a good ANSI-C compiler will complain about a type mismatch if lineM does not match the type of wc():

此处断言真的很繁琐,但是一个好的ANSI-C编译器会报告一个类型不匹配如果lineM的类型不能和wc()匹配:

      In function `Wc_wc`:
      warning: comparison of distinct pointer types lacks a cast

We still have not seen why our filter knows to call wc() to process an input line. Filter_ctor() receives the delegate object as an argument and it can set the interesting components for filter:

我们还没有看到为什么对象知道调用wc()来处理一个输入行。Filter_ctor()接受一个代理对象作为参数并且能为Filter设置感兴趣的组件:

      % Filter ctor {
          struct Filter * self = super_ctor(Filter(), _self, app);
          self —> delegate = va_arg(* app, void *);
          self —> flag = (flagM) respondsTo(self —> delegate, "flag");
          ...
          self —> quit = (quitM) respondsTo(self —> delegate, "quit");
          return self;
      }

The trick is a new statically linked method respondsTo() which may be applied to any Object. It takes an object and a search argument and returns a suitable function pointer if the object has a dynamically linked method corresponding to the search argument.

技巧是一个静态链接的方法respondsTo()能被任何对象使用。它将一个对象和一个要查询的参数作为输入返回一个合适的函数指针,只要次对象有一个和查询的参数对应动态链接的方法。

The returned function pointer could be a selector or the method itself. If we opt for the method, we avoid the selector call when the callback function is called; however, we also avoid the parameter checking which the selector performs. It is better to be safe than to be sorry; therefore, respondsTo() returns a selector.

返回的方法可能是选择器或者是方法本身。如果我们选择此方法,当调用回调函数的时候,我们也禁止了选择器的运行;进而,我们也省去了选择器要做的参数检查,安全一些总比最后道歉好,所以,respondsTo()返回一个选择器。

Designing the search argument is more difficult. Because respondsTo() is a general method for all types of methods we cannot perform type checking at compile time, but we have already shown how the delegate can protect itself. Regardless of type checking we could still let respondsTo() look for the selector it is supposed to return, i.e., the search argument could be the desired selector. Selector names, however, are part of the global name space of a program, i.e., if we look for a selector name we are implicitly restricted to subclasses of the class where the selector was introduced. However, the idea was not to be restricted by inheritance aspects. Therefore, respondsTo() uses a string as the search argument.

对参数搜索更困难些。因为respondsTo()是一个能被所有类型方法使用的通用方法,所以我们不能在编译期进行类型检查,但是我们已经展示了代理如何保护它自己。暂且不考虑类型检查,我们依然让respondsTo()查找应该返回的选择器,或则说检索参数就是期望的选择器。选择器的名字,作为一个在全局名字空间程序的一部分,如果我们查找一个选择器的名字,就是隐式的将它限制为一个类的子类,它在父类中被引入。但是这种想法并没有继承的因素所限制。因为,respondsTo()函数使用一个字符串作为查询参数。

We are left with the problem of associating a string with a dynamically linked method. Logically this can be done in one of two places: when the method is declared in the class description file or when it is implemented in the implementation file. Either way it is a job for ooc because the association between the string tag and the method name must be stored in the class description so that respondsTo() can find it there. The class description, however, is constructed by ooc. We use a simple syntax extension:

我们剩下的问题就是将一个字符串和一个动态链接的方法关联起来。逻辑上,这可以在以下两个地方的一个中完成:在方法声明时写入类的描述文件或者它被实现在实现文件中。不管哪一个方法都是OOC(Object-oriented ANSI-C)的工作,因为字符串标记和方法的名字必须存放在类的描述文件中,方便respondsTo()能够找到它。类描述是由OOC创建的。我们用一个简单的语法扩展:

       % WcClass: Class Wc: Object {
           ...
       %—
       line: int wc (_self, const Object @ filter, \
                       const char * fnm, char * buf);
       wrap: int printFile (_self, const Object @ filter, \
                       const char * fnm);
       quit: int printTotal (_self, const Object @ filter);
       %}

In a class description file like Wc.d a tag may be specified as a label preceding a dynamically linked method. By default, the method name would be used as a tag. An empty label suppresses a tag altogether — in this case respondsTo() cannot find the method. Tags apply to dynamically linked methods, i.e., they are inherited. To make things more flexible, a tag can also be specified as a label in a method header in the implementation file. Such a tag is valid only for the current class.

在像Wc.d类描述文件中标记能被指定为一个放在动态链接方法名字前面的标签。默认情况下,方法的名字会被用作一个标记。一个空的标签一起抑制了一个标记——这种情况下respondsTo()不能找到方法。标记被用于动态连接的方法,它们是继承的。为了使其更灵活,一个标记可以在一个实现文件中被指定为一个方法的标签。这种标记只有现在的类中才合法。

10.6 Implementation

respondsTo() must search the class description for a tag and return the corresponding selector. Thus far, the class description only contains pointers to the methods. Clearly, the method entry in a class description must be extended:

respondsTo() 必须在类的描述中搜索一个标记然后返回对应的选择器。到现在,类的描述中仅包含函数的指针。很明显,在类描述中的方法入口必须被扩展:

       typedef void (* Method) (); // for respondsTo()
       %prot
       struct Method {
           const char * tag; // for respondsTo()
           Method selector; // returned by respondsTo()
           Method method; // accessed by the selector
       };
       % Class Object {
               ...
           Method respondsTo (const _self, const char * tag);

Method is a simple function type defined in the interface file for Object. Each method is recorded in a class description as a component of type struct Method which contains pointers to the tag, the selector, and the actual method. respondsTo() returns a Method. ANSI-C compilers will gripe about implicit casts from and to this type.

Method是一个定义在接口文件中对象的函数。每个方法都作为一个struct Method结构体类型的组件被记录在类的描述文件中,它包含一个指向tag的指针,选择器和实际的函数。respondsTo()返回一个方法。ANSI-C编译器会对隐型转型发出警告或提示。

Given this design, a few more changes are required. In Object.dc we need to change the static initialization of the class descriptions Object and Class to use struct Method:

根据这个设计,还需要一些变动。在Object.dc文件中我们需要改变类描述对象和类的静态初始化来利用struct Method:

       static const struct Class _Object = {
       {   MAGIC, & _Class },
           "Object", & _Object, sizeof(struct Object),
           { "", (Method) 0, (Method) Object_ctor },
           { "", (Method) 0, (Method) Object_dtor },
           { "differ", (Method) differ,(Method) Object_differ },
           ...
       };

The -r report in r.rep uses the link report in va.rep to generate an entry in the class description for the class representation file. The new version of the link report is very simple:

在r.rep描述中-r利用在vap.rep 中Link描述在类的描述中生成一个入口,它是类的代表文件。新版本的link描述非常简单:

       % link // component of metaclass structure
       struct Method `method ;

Finally, the init report in c.rep and c-R.rep uses the meta-ctor-loop in etc.rep to generate the loop which dynamically fills the class description. Here we also have to work with the new types:

最后,在c.rep和c-R.rep 中的init描述利用文件etc.rep中的meta-ctor-loop来生成一个循环,由它动态填充类描述。现在我们也可以和新类型一起使用:

       % meta—ctor—loop // selector/tag/method tuples for `class
       `t while ((selector = va_arg(ap, Method))) `n
       `t { `t const char * tag = va_arg(ap, ` \
                              const char *); `n
           `t `t Method method = va_arg(ap, Method); `n `n
               `{%— `%link—it `}
       `t } `n
       % link—it // check and insert one selector/method pair
       `t `t if (selector == (Method) `method ) `n
       `t `t { `t if (tag) `n
       `t `t `t `t self —> `method .tag = tag, `n
       `t `t `t `t self —> `method .selector = selector; `n
       `t `t `t self —> `method .method = method; `n
       `t `t `t continue; `n
       `t `t } `n

Rather than selector/method pairs we now specify selector/tag/method tuples as arguments to the metaclass constructor. This must be built into the init report in c.rep. Here is the initialization function for Wc generated by ooc:

现在我们用selector/tag/method三元组而不是select/method对来作为元类构造函数的参数。这必须被在文件c.rep的init 描述中创建。下面是一个由ooc生成的Wc的初始化函数:

       static const void * _Wc;
       const void * Wc (void) {
           return _Wc ? _Wc :
               (_Wc = new(WcClass(),
                   "Wc", Object(), sizeof(struct Wc),
                   wc, "line", Wc_wc,
                   printFile, "wrap", Wc_printFile,
                   printTotal, "quit", Wc_printTotal,
                   (void *) 0));
       }

Given the selector/tag/method tuples in a class description, respondsTo() is easy to write. Thanks to the class hierarchy, we can compute how many methods a class description contains and we can implement respondsTo() entirely in the Object class, even though it handles arbitrary classes:

在有三元组的类描述中,respondsTo()函数很容易些出来。幸亏有这样的类结构,我们可以计算类中包含多少个方法并且可以在整Object 类中实现respondsTo(),甚至它操纵整个类:

% respondsTo {

   if (tag && * tag) {
       const struct Class * class = classOf(_self);
       const struct Method * p = & class —> ctor; // first
       int nmeth =
           (sizeOf(class) — offsetof(struct Class, ctor))
           / sizeof(struct Method); // # of Methods
       do
           if (p —> tag && strcmp(p —> tag, tag) == 0)
               return p —> method ? p —> selector : 0;
       while (++ p, —— nmeth);
   }
   return 0;

}

The only drawback is that respondsTo() explicitly contains the first method nameever, ctor, in order to calculate the number of methods from the size of the class description. While ooc could obtain this name from the class description of Object, it would be quite messy to construct a report for ooc to generate respondsTo() in a general fashion.

仅有的不足是respondsTo()方法显式的包含第一个方法名字,即使只是ctor,为了从类的描述的大小计算方法的个数。当然ooc可以从对象的类的描述中得到这个名字,大多数情况下创建一个描述让ooc以一种流行的方式生成respondsTo()。

10.7 Another application— sort

Let us implement a small text sorting program to check if Filter really is reusable, to see how command line options are handled, and to appreciate that a delegate can belong to an arbitrary class.

让我们实现一个小的文本排序程序检验Filter能否可以重用,来看下命令行参数是如何处理的,并且欣赏代理可以属于一个单独的类。

A sort filter must collect all text lines, sort the complete set, and finally write them out. Section 7.7 introduced a List based on a dynamic ring buffer which we can use to collect the lines as long as we add a sorting method. In section 2.5 we implemented a simple String class; if we integrate it with our class hierarchy we can use it to store each line in the List.

排序过滤器必须读取所有的文本行,为集合所有元素排序,最后输出。7.7节介绍一个基于链表的动态环形缓冲区,我们用它来搜集行直到我们加入一个排序方法。在2.5节我们实现一个简单的String类,如果在我们的类结构中集成它,可以使用它来储存链表中的每一个行。

Let us start with the main program which merely creates the filter with its delegate:

让我们从主程序开始,它仅仅创建一个带代理的过滤器:


       int main (int argc, char * argv [])
       {   void * filter = new(Filter(), new(Sort(), 0));
           return mainLoop(filter, argv);
       }

Because we can attach the callback methods to any class, we can create the delegate directly in a subclass of List:

因为我们能对任意类附着回调函数,我们可以直接在List子类中创建代理:

       % SortClass: ListClass Sort: List {
           char rflag;
       %—
           void flags (_self, Object @ filter, char flag);
           int line (_self, const Object @ filter, const char * fnm, \
                                             char * buf);
           int quit (_self, const Object @ filter);
       %}

To demonstrate option handling we recognize ?r as a request to sort in reverse order. All other flags are rejected by the flags() method which has flag as a tag for respondsTo():

为说明选项的处理我们识别-r作为反向排序的的请求。所有的标志将被flags()方法拒绝,它有一个标志作为标签给respondsTo()使用:

       % flag: Sort flags {
       %casts
           assert((flagM) flags == flags);
           if (flag == ’r’)
               self —> rflag = 1;
           else
               fprintf(stderr, "usage: %s [—r] [file...]\n",
               progname(filter)),
           exit(1);
       }

Given String and List, collecting lines is trivial:

在有String和List的情况下,搜集文本行就很容易了:

       % Sort line {
       %casts
           assert((lineM) line == line);
           addLast(self, new(String(), buf));
           return 0;
       }

Once all lines are in, the quit callback takes care of sorting and writing. If there are any lines at all, we let a new method sort() worry about sorting the list, and then we remove each line in turn and let the String object display itself. We can sort in reverse order simply by removing the lines from the back of the list:

一旦所有的行被加进来,回调函数quit关心排序和写出。如果这都是行数据,我们让一个新方法sort()来处理,然后我们依次删除每一行并让String对象显示它自己。我们能很简单的通过从链表的后边开始删除行,来实现逆向排序:

       % Sort quit {
       %casts
           assert((quitM) quit == quit);
           if (count(self))
           {   sort(self);
               do
                   puto(self —> rflag ? takeLast(self)
                                  : takeFirst(self), stdout);
               while (count(self));
           }
           return 0;
       }

What about sort()? ANSI-C defines the library function qsort() for sorting arbitrary arrays based on a comparison function. Luckily, List is implemented as a ring buffer in an array, i.e., if we implement sort() as a method of List we should have very little trouble:

sort()是什么实现的?ANSI-C定义库函数qsort()以一个比较函数的基础对数组排序。幸运的是,List在树组中被实现为一个环形缓冲区,如果我们将sort()实现为List的一个方法是很容易的:

       static int cmp (const void * a, const void * b)
       {
           return differ(* (void **) a, * (void **) b);
       }
       % List sort {
       %casts
           if (self —> count)
           {   while (self —> begin + self —> count > self —> dim)
                   addFirst(self, takeLast(self));
               qsort(self —> buf + self —> begin, self —> count,
                          sizeof self —> buf[0], cmp);
           }
       }

If there are any list elements, we rotate the list until it is a single region of the buffer and then pass the list to qsort(). The comparison function sends differ() to the list elements themselves — String_differ was based on strcmp() and can, therefore, be (ab-)used as a comparison function.

如果有任意的链表元素,我们旋转链表直到成为一个单一的没有重叠的内存空间,然后将其传给qsort()函数。比较函数向链表元素本身发送differ()函数---String_differ是以strcmp()为基础的,所以可以作为比较函数.

10.8 Summary

An object points to its class description and the class description points to all the dynamically linked methods for the object. Therefore, an object can be asked if it will respond to a particular method. respondsTo() is a statically linked method for Object. It takes an object and a string tag as search argument and returns the appropriate selector if the tag matches a method for the object.

对象指的是它所属的类的描述,而类的描述指所有的动态链接的方法。 所以,可以询问一个对象是否能响应一个特定的方法.respondsTo()是对象的一个静态连接的方法,它以对象和一个字符串标识作为参数,当存在与此标记匹配的方法时,返回合适的函数选择器。

Tags can be specified to ooc as labels on the prototypes of dynamically linked methods in the class definition file, and as labels on a method header in the imple1__ mentation file; the latter have precedence. By default, the method name is used as a tag. Empty tags cannot be found. For the implementation of respondsTo() a method is passed to a metaclass constructor as a triple selector/tag/method.

标记可以被指定为动态连接方法原型的标签记录在类定义文件中,并且在实现文件中方法头上作为标签;后边的有优先权。默认的,方法明作为一个标签。空标签将不能被找到。respondTo()的实现是传入一个选择器、标记、方法组成的三元组作为元类构造函数的参数。

Given respondsTo(), we can implement delegates: a client object announces itself as a delegate object to a host object. The host queries the client with respondsTo() if it can answer certain method calls. If it does, the host will use these methods to inform the client of some state changes.

根据respondsTo(),我们能够实现代理:一个客户对象向主对象声明自己是一个代理对象。主对象通过respondsTo()方法查询是否能够响应特定的方法调用。如果可以,主对象将用那些方法来通知客户某些状态的改变。

Delegates are preferable to registering callback functions and to abstract base classes for defining the communication between a host and a client. A callback function cannot be a method because the host does not have an object to call the method with. An abstract base class imposes unnecessary restrictions on application-oriented development of the class hierarchy. Similar to callback functions, we can implement for delegates just those methods which are interesting for a particular situation. The set of possible methods can be much larger.

代理倾向于注册为回调函数并且抽象基类用来定义客户和调用者之间的通信。一个回调方法不能是一个单独的方法,因为调用者不能通过某一对象来调用它。抽象基类给面向应用的开发的类结构强加了不必要的约束。同样的对于回调函数,我们将那些对于特定条件感兴趣的方法实现为代理。可能适应的方法有相当的一部分。

An application framework consists of one or more objects which provide the typical structure of an application. If it is well designed, it can save a great deal of routine coding. Delegates are a very convenient technique to let the application framework interact with the problem-specific code.

一个应用程序框架有一个或多个提供典型应用结构的对象组成。如果它是良好设计的,它可以节省很多例行的编码。代理是很方便的技术可以帮应用框架和问题相关的编码良好的一起工作。

10.9 Exercises

Filter implements a standard command line where options precede filename arguments, where flags can be bundled, and where option values can be bundled or specified as separate arguments. Unfortunately, pr(1) is a commonly available program that does not fit this pattern. Is there a general solution? Can a flag introduce two or more argument values which all appear as separate arguments?

过滤器(Filter)实现标准的命令行,它的选项在文件名参数的前边而且是标记是打包到一起的,选项的值可以是打包的或者是单独的参数。不幸的是,pr(1)(问题一)是一个通用的程序不符合这个模式。有一个通用的解决方案吗?一个标记能带两个或多个作为独立参数的参数值?

The line callback should be modified so that binary files can be handled correctly. Does it make sense to provide a byte callback? What is an alternative?

line回调可以被更改从而可以正确的处理二进制文件。它要提供一个byte回调?替代方案是什么?

A much more efficient, although not portable, implementation would try to map a file into memory if possible. The callback interface does not necessarily have to be modified but a modification would make it more robust.

一个更高效,尽管不可移植的实现试着映射一个文件到内存如果可能的话。回调的接口不是必须被改变,不过修改后能让它更健壮。

respondsTo() has to know the name of the first struct Method component of every class description. The reports -r in r-R.rep or rather init in c-R.rep can be modified to define a structure to circumvent this problem.

respondsTo()必须知道每一个类描述文件中的struct Method组件的第一个名字。描述中r-R.rep中的-r或者c-R.rep中的init可以被修改来定义一个结构来满足这一问题。

The init report can be modified to generate a puto() method for Class which uses the same technique as respondsTo() to display all method tags and addresses.

init描述可以修改来为类生成一个puto()方法,它采用和respondsTo()同样的技术来展示所有的方法和地址。

Piping the output of our sort program into the official sort(1) for checking may produce a surprise:

通过管道将我们的排序程序的输出作为官方使用的sort(1)程序来检验,可能会让人吃惊:

       $ sort —r Sort.d | /usr/bin/sort —c —r
       sort: disorder: int quit (_self, const Object @ filter);

There are more efficient ways for List_sort() to compact the list in the ring buffer before passing it to qsort(). Are we really correct in rotating it?

List_sort()的方法更高效,它在传给qsort()之前,将链表装进一个环形缓冲区。我们的旋转操作真的正确吗?

个主工具