**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/xcom_arg.py
"""
        Implements xcomresult['some_result_key']
        """
        return XComArg(operator=self.operator, key=item)
    def __str__(self):
        """
        Backward compatibility for old-style jinja used in Airflow Operators
        **Example**: to use XComArg at BashOperator::
            BashOperator(cmd=f"... { xcomarg } ...")
        :return:
        """
        xcom_pull_kwargs = [f"task_ids='{self.operator.task_id}'",
                            f"dag_id='{self.operator.dag.dag_id}'",
                            ]
        if self.key is not None:
            xcom_pull_kwargs.append(f"key='{self.key}'")
        xcom_pull_kwargs = ", ".join(xcom_pull_kwargs)
        xcom_pull = f"task_instance.xcom_pull({xcom_pull_kwargs})"
        return xcom_pull
    @property
    def operator(self) -> BaseOperator:
        """Returns operator of this XComArg"""
        return self._operator
    @property
    def key(self) -> str:
        """Returns keys of this XComArg"""
        return self._key
    def set_upstream(self, task_or_task_list: Union[BaseOperator, List[BaseOperator]]):