**Description**

Currently `BaseOperator` and `XComArgs` implements the same logic of `__lshift__`, `__rshift__` and others "chain" operations (`>>`, `<<`). It seems resonable to abstract this logic into `TaskMixin` to introduce some DRYness especially because the new concept of `TaskGroup` will also implement the same methods.

**Use case / motivation**

Limit duplication of logic.

**Related Issues**

https://github.com/apache/airflow/pull/10827#issuecomment-691927501
#10153 

airflow/models/baseoperator.py
"""Stringified DAGs and operators contain exactly these fields."""
        if not cls.__serialized_fields:
            cls.__serialized_fields = frozenset(
                vars(BaseOperator(task_id='test')).keys() - {
                    'inlets', 'outlets', '_upstream_task_ids', 'default_args', 'dag', '_dag',
                    '_BaseOperator__instantiated',
                } | {'_task_type', 'subdag', 'ui_color', 'ui_fgcolor', 'template_fields'})
        return cls.__serialized_fields
    def is_smart_sensor_compatible(self):
        """
        Return if this operator can use smart service. Default False.
        """
        return False
def chain(*tasks: Union[BaseOperator, Sequence[BaseOperator]]):
    r"""
    Given a number of tasks, builds a dependency chain.
    Support mix airflow.models.BaseOperator and List[airflow.models.BaseOperator].
    If you want to chain between two List[airflow.models.BaseOperator], have to
    make sure they have same length.
    .. code-block:: python
         chain(t1, [t2, t3], [t4, t5], t6)
    is equivalent to::
         / -> t2 -> t4 \
       t1               -> t6
         \ -> t3 -> t5 /
    .. code-block:: python