**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
"""
        Called for [This Operator] | [Operator], The inlets of other
        will be set to pickup the outlets from this operator. Other will
        be set as a downstream task of this operator.
        """
        if isinstance(other, BaseOperator):
            if not self._outlets and not self.supports_lineage:
                raise ValueError("No outlets defined for this operator")
            other.add_inlets([self.task_id])
            self.set_downstream(other)
        else:
            raise TypeError(f"Right hand side ({other}) is not an Operator")
        return self
    
    def __gt__(self, other):
        """
        Called for [Operator] > [Outlet], so that if other is an attr annotated object
        it is set as an outlet of this Operator.
        """
        if not isinstance(other, Iterable):
            other = [other]
        for obj in other:
            if not attr.has(obj):
                raise TypeError(f"Left hand side ({obj}) is not an outlet")
        self.add_outlets(other)
        return self
    def __lt__(self, other):