Laravel源码阅读之Container singleton与make

阅读说明

软件版本

  • Laravel v11.9.2

前言

主要介绍singleton做了哪些事情、作用、为什么要这么做以及最终调用时(make)的过程。

涉及到singleton和make的源码过程。

Illuminate\Container\Container::singleton

绑定一个单例,官方文档的解释:

singleton 方法(绑定单例)将类或接口绑定到容器中,该类或接口只能解析一次。一旦解决了单一实例绑定,在对容器的后续调用中,将返回相同的对象实例:

看完本文以后好好细品这段解释。

1
2
3
4
5
6
7
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;
 
$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

上源码Illuminate\Container\Container

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * The container's bindings.
 *
 * @var array[]
 */
protected $bindings = [];
/**
 * The container's shared instances.
 *
 * @var object[]
 */
protected $instances = [];
/**
 * The registered type aliases.
 *
 * @var string[]
 */
protected $aliases = [];
/**
 * An array of the types that have been resolved.
 *
 * @var bool[]
 */
protected $resolved = [];
/**
 * All of the registered rebound callbacks.
 *
 * @var array[]
 */
protected $reboundCallbacks = [];
/**
 * Register a shared binding in the container.
 *
 * @param  string  $abstract
 * @param  \Closure|string|null  $concrete
 * @return void
 */
public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}
/**
 * Register a binding with the container.
 *
 * @param  string  $abstract
 * @param  \Closure|string|null  $concrete
 * @param  bool  $shared
 * @return void
 *
 * @throws \TypeError
 */
public function bind($abstract, $concrete = null, $shared = false)
{
    $this->dropStaleInstances($abstract);

    // If no concrete type was given, we will simply set the concrete type to the
    // abstract type. After that, the concrete type to be registered as shared
    // without being forced to state their classes in both of the parameters.
    if (is_null($concrete)) {
        $concrete = $abstract;
    }

    // If the factory is not a Closure, it means it is just a class name which is
    // bound into this container to the abstract type and we will just wrap it
    // up inside its own Closure to give us more convenience when extending.
    if (! $concrete instanceof Closure) {
        if (! is_string($concrete)) {
            throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
        }

        $concrete = $this->getClosure($abstract, $concrete);
    }

    $this->bindings[$abstract] = compact('concrete', 'shared');

    // If the abstract type was already resolved in this container we'll fire the
    // rebound listener so that any objects which have already gotten resolved
    // can have their copy of the object updated via the listener callbacks.
    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}
/**
 * Drop all of the stale instances and aliases.
 *
 * @param  string  $abstract
 * @return void
 */
protected function dropStaleInstances($abstract)
{
    unset($this->instances[$abstract], $this->aliases[$abstract]);
}
/**
 * Get the Closure to be used when building a type.
 *
 * @param  string  $abstract
 * @param  string  $concrete
 * @return \Closure
 */
protected function getClosure($abstract, $concrete)
{
    return function ($container, $parameters = []) use ($abstract, $concrete) {
        if ($abstract == $concrete) {
            return $container->build($concrete);
        }

        return $container->resolve(
            $concrete, $parameters, $raiseEvents = false
        );
    };
}
/**
 * Determine if the given abstract type has been resolved.
 *
 * @param  string  $abstract
 * @return bool
 */
public function resolved($abstract)
{
    if ($this->isAlias($abstract)) {
        $abstract = $this->getAlias($abstract);
    }

    return isset($this->resolved[$abstract]) ||
           isset($this->instances[$abstract]);
}
/**
 * Determine if a given string is an alias.
 *
 * @param  string  $name
 * @return bool
 */
public function isAlias($name)
{
    return isset($this->aliases[$name]);
}
/**
 * Get the alias for an abstract if available.
 *
 * @param  string  $abstract
 * @return string
 */
public function getAlias($abstract)
{
    return isset($this->aliases[$abstract])
                ? $this->getAlias($this->aliases[$abstract])
                : $abstract;
}
/**
 * Fire the "rebound" callbacks for the given abstract type.
 *
 * @param  string  $abstract
 * @return void
 */
protected function rebound($abstract)
{
    $instance = $this->make($abstract);

    foreach ($this->getReboundCallbacks($abstract) as $callback) {
        $callback($this, $instance);
    }
}
/**
 * Get the rebound callbacks for a given type.
 *
 * @param  string  $abstract
 * @return array
 */
protected function getReboundCallbacks($abstract)
{
    return $this->reboundCallbacks[$abstract] ?? [];
}

在这里你可以把make想象成一个黑匣子,它会把你给的抽象处理成实例返回给你,并且这个过程中会替你解决抽象所需的各种依赖参数(这就是依赖注入dependency injection)。

重点看bind()方法做的事情:

  • P1:删除过时的实例。
  • P2:具体(concrete)是空时,具体就是抽象(abstract)。
  • P3:具体不是匿名函数,而是字符串时,将具体处理成匿名函数。
  • P4:将抽象和具体(此时是匿名函数)作为key value,设置到正在绑定中的对象(bindings)。
  • P5:当抽象已被解决(resolved)或者已有实例(instances)时,进行篮板球(rebound)处理,将抽象重新实例化并调用抽象设置的回调函数(reboundCallbacks)。

疑问

  • Q1:P1为什么需要从已有实例和别名中删除抽象?
  • Q2:P2什么情况下具体可以是空的?
  • Q3:P3为什么要将具体处理成匿名函数,目的是什么?
  • Q4:P4设置到正在绑定中的对象的目的是什么?
  • Q5:P5什么场景下需要执行篮板球?
  • Q6:绑定的单例什么时候会被调用?

例子

来看具体的调用例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use Illuminate\Foundation\PackageManifest;
use Illuminate\Foundation\Mix;
use Illuminate\Filesystem\Filesystem;

// 例子A
$this->singleton(PackageManifest::class, fn () => new PackageManifest(
    new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));

// 例子B
$this->singleton(Mix::class);

// 例子C
$this->app->singleton(
    \Illuminate\Contracts\Http\Kernel::class,
    \Illuminate\Foundation\Http\Kernel::class,
);

例子A

定义处\Illuminate\Foundation\Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/**
 * Register the basic bindings into the container.
 *
 * @return void
 */
protected function registerBaseBindings()
{
    static::setInstance($this);

    $this->instance('app', $this);

    $this->instance(Container::class, $this);
    $this->singleton(Mix::class);

    $this->singleton(PackageManifest::class, fn () => new PackageManifest(
        new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
    ));
}

调用有两处:

  • A1:\Illuminate\Foundation\Bootstrap\RegisterFacades::bootstrap
  • A2:\Illuminate\Foundation\Application::registerConfiguredProviders

A1要比A2先调用。

A1

先来看A1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * Bootstrap the given application.
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @return void
 */
public function bootstrap(Application $app)
{
    Facade::clearResolvedInstances();

    Facade::setFacadeApplication($app);

    AliasLoader::getInstance(array_merge(
        $app->make('config')->get('app.aliases', []),
        $app->make(PackageManifest::class)->aliases()
    ))->register();
}

可以看到调用了make进行实例化。

make关键代码\Illuminate\Foundation\Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @return mixed
 */
public function make($abstract, array $parameters = [])
{
    $this->loadDeferredProviderIfNeeded($abstract = $this->getAlias($abstract));

    return parent::make($abstract, $parameters);
}

\Illuminate\Container\Container

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
/**
 * Resolve the given type from the container.
 *
 * @param  string|callable  $abstract
 * @param  array  $parameters
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 */
public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}
/**
 * Resolve the given type from the container.
 *
 * @param  string|callable  $abstract
 * @param  array  $parameters
 * @param  bool  $raiseEvents
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 * @throws \Illuminate\Contracts\Container\CircularDependencyException
 */
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
    ...
      
    if (is_null($concrete)) {  
        $concrete = $this->getConcrete($abstract);  
    }
    
    // We're ready to instantiate an instance of the concrete type registered for
    // the binding. This will instantiate the types, as well as resolve any of
    // its "nested" dependencies recursively until all have gotten resolved.
    $object = $this->isBuildable($concrete, $abstract)
        ? $this->build($concrete)
        : $this->make($concrete);

    ...

    // If the requested type is registered as a singleton we'll want to cache off
    // the instances in "memory" so we can return it later without creating an
    // entirely new instance of an object on each subsequent request for it.
    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    ...

    // Before returning, we will also set the resolved flag to "true" and pop off
    // the parameter overrides for this build. After those two things are done
    // we will be ready to return back the fully constructed class instance.
    $this->resolved[$abstract] = true;

    return $object;
}
/**
 * Get the concrete type for a given abstract.
 *
 * @param  string|callable  $abstract
 * @return mixed
 */
protected function getConcrete($abstract)
{
    // If we don't have a registered resolver or concrete for the type, we'll just
    // assume each type is a concrete name and will attempt to resolve it as is
    // since the container should be able to resolve concretes automatically.
    if (isset($this->bindings[$abstract])) {
        return $this->bindings[$abstract]['concrete'];
    }

    return $abstract;
}
/**
 * Determine if the given concrete is buildable.
 *
 * @param  mixed  $concrete
 * @param  string  $abstract
 * @return bool
 */
protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}
/**
 * Instantiate a concrete instance of the given type.
 *
 * @param  \Closure|string  $concrete
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 * @throws \Illuminate\Contracts\Container\CircularDependencyException
 */
public function build($concrete)
{
    // If the concrete type is actually a Closure, we will just execute it and
    // hand back the results of the functions, which allows functions to be
    // used as resolvers for more fine-tuned resolution of these objects.
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }
    ...
}

例子A经过绑定单例处理后,bindings对应结构在make调用时大致如下:

1
2
3
4
5
6
7
8
[
    'Illuminate\Foundation\PackageManifest' => [
        'shared' => true,
        'concrete' => function () {
            return new PackageMainifest(new Filesystem, '/system_path/path/your_project', '/system_path/path/your_project/bootstrap/cache/packages.php')
        }
    ]
]

注意这里concrete展示的匿名函数为了方便演示稍作了处理。

make从bindings.Illuminate\Foundation\PackageManifest.concrete取出匿名函数,再进到build()函数中执行了该匿名函数。

因为singleton(shared: true)的缘故,首次make时(指A1)会把实例(concrete匿名函数的调用结果)设置到容器的instances成员属性中,后续再make的时候(指A2)会直接从instances中取出。

这样实现的好处:

  • 可以在需要用到抽象的时候再从bindings中取出提前预定义好实例化的方式进行实例化(回答Q3、Q6)。
  • 统一处理成匿名函数(第一个参数是Illuminate\Foundation\Application容器,第二个参数则是传递给make的第二个参数),统一口径方便make时处理(回答Q3)。就好比有了SAPI接口规范,就能通过不同的方式实现PHP与各种web服务器或服务打交道,比如CLI、CGI和FastCGI都是根据规范实现的模块。

设置到bindings是为了在make的时候可以取到预定义的绑定。(回答Q4)

A2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * Register all of the configured providers.
 *
 * @return void
 */
public function registerConfiguredProviders()
{
    $providers = Collection::make($this->make('config')->get('app.providers'))
                    ->partition(fn ($provider) => str_starts_with($provider, 'Illuminate\\'));

    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());

    $this->fireAppCallbacks($this->registeredCallbacks);
}

关键代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
    ...

    // If an instance of the type is currently being managed as a singleton we'll
    // just return an existing instance instead of instantiating new instances
    // so the developer can keep using the same objects instance every time.
    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    ...

    if (is_null($concrete)) {
        $concrete = $this->getConcrete($abstract);
    }
    
    ...
}

例子B

定义处在例子A已经贴过了,这里就不重复贴了。

调用处:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (! function_exists('mix')) {
    /**
     * Get the path to a versioned Mix file.
     *
     * @param  string  $path
     * @param  string  $manifestDirectory
     * @return \Illuminate\Support\HtmlString|string
     *
     * @throws \Exception
     */
    function mix($path, $manifestDirectory = '')
    {
        return app(Mix::class)(...func_get_args());
    }
}
if (! function_exists('app')) {
    /**
     * Get the available container instance.
     *
     * @param  string|null  $abstract
     * @param  array  $parameters
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Foundation\Application|mixed
     */
    function app($abstract = null, array $parameters = [])
    {
        if (is_null($abstract)) {
            return Container::getInstance();
        }

        return Container::getInstance()->make($abstract, $parameters);
    }
}

经过singleton处理以后,bindings结构大致如下:

1
2
3
4
5
6
7
8
[
    'Illuminate\Foundation\Mix' => [
        'shared' => true,
        'concrete' => function ($container, $parameters = []) {
            return $container->build('Illuminate\Foundation\Mix');
        }
    ]
]

注意这里的concrete匿名函数为了演示稍加处理。

和例子A类似,不多做赘述,只讲差异的地方。

concrete匿名函数被build调用,然后又执行匿名函数中的build,即\Illuminate\Foundation\Application::build\Illuminate\Container\Container::build

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function build($concrete)
{
    ...
    try {
        $reflector = new ReflectionClass($concrete);
    } catch (ReflectionException $e) {
        throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
    }

    // If the type is not instantiable, the developer is attempting to resolve
    // an abstract type such as an Interface or Abstract Class and there is
    // no binding registered for the abstractions so we need to bail out.
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    ...
    
    $constructor = $reflector->getConstructor();

    // If there are no constructors, that means there are no dependencies then
    // we can just resolve the instances of the objects right away, without
    // resolving any other types or dependencies out of these containers.
    if (is_null($constructor)) {
        ...

        return new $concrete;
    }

    ...
}

做了以下几件事:

  • 用ReflectionClass反射Illuminate\Foundation\Mix类。
  • 检查类是否可实例化,不能就会抛Illuminate\Contracts\Container\BindingResolutionException异常。
  • 然后再检查是否有构造函数,Mix类没有,所以最后用new进行实例化。
    • 如果有构造函数会怎样?看接下来的例子C。

如果你提供的类非常简单,甚至都没有构造函数,抽象即具体,而且需要单例的情况下,就可以使用singleton只传一个参数(回答Q2)。

例子C

定义处Illuminate\Foundation\Configuration\ApplicationBuilder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/**
 * Register the standard kernel classes for the application.
 *
 * @return $this
 */
public function withKernels()
{
    $this->app->singleton(
        \Illuminate\Contracts\Http\Kernel::class,
        \Illuminate\Foundation\Http\Kernel::class,
    );

    $this->app->singleton(
        \Illuminate\Contracts\Console\Kernel::class,
        \Illuminate\Foundation\Console\Kernel::class,
    );

    return $this;
}

调用处Illuminate\Foundation\Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use Illuminate\Contracts\Http\Kernel as HttpKernelContract;
/**
 * Handle the incoming HTTP request and send the response to the browser.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return void
 */
public function handleRequest(Request $request)
{
    $kernel = $this->make(HttpKernelContract::class);

    $response = $kernel->handle($request)->send();

    $kernel->terminate($request, $response);
}

经过singleton处理以后,bindings结构大致如下:

1
2
3
4
5
6
7
8
[
    'Illuminate\Contracts\Http\Kernel::class' => [
        'shared' => true,
        'concrete' => function ($container, $parameters = []) {
            return $container->resolve('Illuminate\Foundation\Http\Kernel', [], false);
        }
    ]
]

注意这里的concrete匿名函数为了演示稍加处理。

看一眼Illuminate\Foundation\Http\Kernel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Routing\Router;
/**
 * Create a new HTTP kernel instance.
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 */
public function __construct(Application $app, Router $router)
{
    $this->app = $app;
    $this->router = $router;

    $this->syncMiddlewareToRouter();
}

同样只讲差异的部分。

和例子A、B一样,经过make->resolve,concrete匿名函数被build调用。

然后会执行匿名函数中的resolve,即Illuminate\Foundation\Application::resolve然后Illuminate\Container\Container::resolve

可以看到第三个参数raiseEvents为false,作用是不会经过头尾的钩子函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
    ...
    
    if (is_null($concrete)) {
        $concrete = $this->getConcrete($abstract);
    }

    $object = $this->isBuildable($concrete, $abstract)
        ? $this->build($concrete)
        : $this->make($concrete);
    
    ...

    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    ...

    $this->resolved[$abstract] = true;

    ...

    return $object;
}

Illuminate\Foundation\Http\Kernel最终会被build调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public function build($concrete)
{
    ...
    
    try {
        $reflector = new ReflectionClass($concrete);
    } catch (ReflectionException $e) {
        throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
    }

    ...

    $this->buildStack[] = $concrete;

    $constructor = $reflector->getConstructor();

    ...

    $dependencies = $constructor->getParameters();

    // Once we have all the constructor's parameters we can create each of the
    // dependency instances and then use the reflection instances to make a
    // new instance of this class, injecting the created dependencies in.
    try {
        $instances = $this->resolveDependencies($dependencies);
    } catch (BindingResolutionException $e) {
        array_pop($this->buildStack);

        throw $e;
    }

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}

区别例子B的地方在于,这次是有构造函数了,而且有两个类参数Illuminate\Contracts\Foundation\ApplicationIlluminate\Routing\Router。在经过依赖注入处理以后(依赖注入不在本文讨论范文内,不过多开展),会将实例化好的参数传给Kernel的反射类的newInstanceArgs进行实例化。

到这里阅读基本结束了,三种情况基本都能覆盖到关键的地方。

回答一下Q1的疑问,需要从instances删除抽象答案很明显,因为make完以后会将实例设置到instances中。至于为什么要从aliases中删除抽象,是因为make中有从aliases获取抽象,这个和aliases的作用有关,也不开展了,不删的话肯定会干扰到make的逻辑。

至于Q5,暂时还没找到例子,留点优化空间(狗头,后面遇到了再做补充)。

总结

singleton、make大致逻辑和使用过程:

将你提供的抽象(abstract)和具体(concrete),将具体统一处理成匿名函数(Closure),绑定(抽象 => 具体)在容器的bindings成员属性(数组)中,后续你想用的时候,通过make调用,从bindings属性中取出匿名函数,如果匿名函数是你提供的,就按你定义的来实例化,否则会先反射该类(ReflectionClass),检查类是否可实例化(isInstantiable()),不能会抛异常(BindingResolutionException),再检查是否有构造函数(getConstructor()),没有会通过new实例化返回,有则再根据构造函数的参数(getParameters())通过依赖注入(resolveDependencies())解决参数依赖问题,最后调用反射类的newInstanceArgs()的方式实例化。最后将实例化的类(抽象=>实例)设置到容器的instances成员属性(数组)中,并且将resolved成员属性(数组)中将抽象标记为true,代表着该抽象已完成了实例化处理,下次再make时可以直接从instances(通过isset该属性来判断)中取出。

Built with Hugo
主题 StackJimmy 设计